diff -Nru geomet-0.2.1.post1/AUTHORS.txt geomet-1.0.0/AUTHORS.txt --- geomet-0.2.1.post1/AUTHORS.txt 2020-01-27 11:00:26.000000000 +0000 +++ geomet-1.0.0/AUTHORS.txt 2022-11-12 11:31:55.000000000 +0000 @@ -5,3 +5,7 @@ Sean Gillies - August 2014 Tom Caruso - December 2018 Paul Bryan - August 2019 +Ram Rachum - June 2020 +Andrew Chapkowski - June 2020 +Vadim Kozyrevskii - January 2021 +Stian Jensen - January 2022 diff -Nru geomet-0.2.1.post1/debian/changelog geomet-1.0.0/debian/changelog --- geomet-0.2.1.post1/debian/changelog 2020-08-05 12:25:04.000000000 +0000 +++ geomet-1.0.0/debian/changelog 2023-01-08 11:35:46.000000000 +0000 @@ -1,3 +1,12 @@ +geomet (1.0.0-1) unstable; urgency=medium + + * Releasing debian version 1.0.0-1. + * Updating to standards version 4.6.2. + * Updating years in copyright for 2023. + * Adding upstream version 1.0.0. + + -- Simon Spöhel Sun, 08 Jan 2023 12:35:46 +0100 + geomet (0.2.1.post1-2) unstable; urgency=medium * Uploading to unstable. diff -Nru geomet-0.2.1.post1/debian/control geomet-1.0.0/debian/control --- geomet-0.2.1.post1/debian/control 2020-08-05 12:25:04.000000000 +0000 +++ geomet-1.0.0/debian/control 2023-01-08 11:25:45.000000000 +0000 @@ -10,7 +10,7 @@ python3-setuptools, python3-six, Rules-Requires-Root: no -Standards-Version: 4.5.0 +Standards-Version: 4.6.2 Homepage: https://github.com/geomet/geomet Vcs-Browser: https://salsa.debian.org/simon-guest/geomet Vcs-Git: https://salsa.debian.org/simon-guest/geomet.git diff -Nru geomet-0.2.1.post1/debian/copyright geomet-1.0.0/debian/copyright --- geomet-0.2.1.post1/debian/copyright 2020-08-05 12:25:04.000000000 +0000 +++ geomet-1.0.0/debian/copyright 2023-01-08 11:24:43.000000000 +0000 @@ -4,11 +4,11 @@ Source: https://pypi.python.org/pypi/geomet Files: * -Copyright: 2013-2020 Lars Butler +Copyright: 2013-2023 Lars Butler License: Apache-2.0 Files: debian/* -Copyright: 2018-2020 Simon Spöhel +Copyright: 2018-2023 Simon Spöhel License: Apache-2.0 License: Apache-2.0 diff -Nru geomet-0.2.1.post1/geomet/esri.py geomet-1.0.0/geomet/esri.py --- geomet-0.2.1.post1/geomet/esri.py 1970-01-01 00:00:00.000000000 +0000 +++ geomet-1.0.0/geomet/esri.py 2022-11-12 11:31:55.000000000 +0000 @@ -0,0 +1,248 @@ +# Copyright 2013 Lars Butler & individual contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import json +import geomet + + +def load(source_file): + """ + Converts Esri Json File to GeoJSON. + + :param source_file: + Path to a file that contains the Esri JSON data. + + :returns: + A GeoJSON `dict` representing the geometry read from the file. + + """ + return json.load(source_file) + + +def loads(string): + """ + Construct a GeoJSON `dict` from Esri JSON (string/dict). + + :param string: + The Esri JSON geometry representation + + :returns: + A GeoJSON `dict` representing the geometry read from the file. + """ + data = json.loads(string) + + if "rings" in data: + return _esri_to_geojson_convert["rings"](data) + elif "paths" in data: + return _esri_to_geojson_convert["paths"](data) + elif "x" in data or "y" in data: + return _esri_to_geojson_convert["x"](data) + elif "points" in data: + return _esri_to_geojson_convert["points"](data) + else: + raise geomet.InvalidGeoJSONException("Invalid EsriJSON: %s" % string) + + +def dump(obj, dest_file, srid=None): + """ + Converts GeoJSON to Esri JSON File. + """ + return json.dump(dumps(obj, srid=srid), dest_file) + + +def dumps(obj, srid=None): + """ + Dump a GeoJSON-like `dict` to a Esri JSON. + + :param string: + The GeoJSON geometry representation + :param int: + The default SRID value if none is present. + + """ + if "type" in obj and obj["type"].lower() in _gj_to_esri.keys(): + convert = _gj_to_esri[obj["type"].lower()] + return convert(obj, srid=srid) + else: + raise geomet.InvalidGeoJSONException("Invalid GeoJSON type %s" % obj) + + +def _extract_geojson_srid(obj): + """ + Extracts the SRID code (WKID code) from geojson. If not found, SRID=4326 + + :returns: Integer + """ + meta_srid = obj.get("meta", {}).get("srid", None) + # Also try to get it from `crs.properties.name`: + crs_srid = obj.get("crs", {}).get("properties", {}).get("name", None) + if crs_srid is not None: + # Shave off the EPSG: prefix to give us the SRID: + crs_srid = crs_srid.replace("EPSG:", "") + + if ( + meta_srid is not None + and crs_srid is not None + and str(meta_srid) != str(crs_srid) + ): + raise ValueError( + "Ambiguous CRS/SRID values: %s and %s" % (meta_srid, crs_srid) + ) + srid = meta_srid or crs_srid + + return srid or 4326 + + +def _dump_geojson_point(obj, srid=None): + """ + Loads GeoJSON to Esri JSON for Geometry type Point. + + + """ + coordkey = "coordinates" + coords = obj[coordkey] + srid = _extract_geojson_srid(obj) or srid + return {"x": coords[0], "y": coords[1], "spatialReference": {"wkid": srid}} + + +def _dump_geojson_multipoint(obj, srid=None): + """ + Loads GeoJSON to Esri JSON for Geometry type MultiPoint. + + """ + coordkey = "coordinates" + srid = _extract_geojson_srid(obj) or srid + return {"points": obj[coordkey], "spatialReference": {"wkid": srid}} + + +def _dump_geojson_polyline(obj, srid=None): + """ + Loads GeoJSON to Esri JSON for Geometry type LineString and + MultiLineString. + + """ + coordkey = "coordinates" + if obj["type"].lower() == "linestring": + coordinates = [obj[coordkey]] + else: + coordinates = obj[coordkey] + srid = _extract_geojson_srid(obj) or srid + return {"paths": coordinates, "spatialReference": {"wkid": srid}} + + +def _dump_geojson_polygon(data, srid=None): + """ + Loads GeoJSON to Esri JSON for Geometry type Polygon or MultiPolygon. + + """ + coordkey = "coordinates" + coordinates = data[coordkey] + typekey = ([d for d in data if d.lower() == "type"] or ["type"]).pop() + if data[typekey].lower() == "polygon": + coordinates = [coordinates] + part_list = [] + for part in coordinates: + if len(part) == 1: + part_list.append(part[0]) + else: + for seg in part: + part_list.append([list(coord) for coord in seg]) + srid = _extract_geojson_srid(data) or srid + return {"rings": part_list, "spatialReference": {"wkid": srid}} + + +def _to_gj_point(obj): + """ + Dump a Esri JSON Point to GeoJSON Point. + + :param dict obj: + A EsriJSON-like `dict` representing a Point. + + + :returns: + GeoJSON representation of the Esri JSON Point + """ + if obj.get("x", None) is None or obj.get("y", None) is None: + return {"type": "Point", "coordinates": ()} + return {"type": "Point", "coordinates": (obj.get("x"), obj.get("y"))} + + +def _to_gj_polygon(obj): + """ + Dump a EsriJSON-like Polygon object to GeoJSON. + + Input parameters and return value are the POLYGON equivalent to + :func:`_to_gj_point`. + """ + + def split_part(a_part): + part_list = [] + for item in a_part: + if item is None: + if part_list: + yield part_list + part_list = [] + else: + part_list.append((item[0], item[1])) + if part_list: + yield part_list + + part_json = [list(split_part(part)) for part in obj["rings"]] + return {"type": "MultiPolygon", "coordinates": part_json} + + +def _to_gj_multipoint(data): + """ + Dump a EsriJSON-like MultiPoint object to GeoJSON-dict. + + Input parameters and return value are the MULTIPOINT equivalent to + :func:`_to_gj_point`. + + :returns: `dict` + """ + + return {"type": "MultiPoint", "coordinates": [pt for pt in data["points"]]} + + +def _to_gj_polyline(data): + """ + Dump a GeoJSON-like MultiLineString object to WKT. + + Input parameters and return value are the MULTILINESTRING equivalent to + :func:`_dump_point`. + """ + return { + "type": "MultiLineString", + "coordinates": [ + [((pt[0], pt[1]) if pt else None) for pt in part] + for part in data["paths"] + ], + } + + +_esri_to_geojson_convert = { + "x": _to_gj_point, + "y": _to_gj_point, + "points": _to_gj_multipoint, + "rings": _to_gj_polygon, + "paths": _to_gj_polyline, +} + +_gj_to_esri = { + "point": _dump_geojson_point, + "multipoint": _dump_geojson_multipoint, + "linestring": _dump_geojson_polyline, + "multilinestring": _dump_geojson_polyline, + "polygon": _dump_geojson_polygon, + "multipolygon": _dump_geojson_polygon, +} diff -Nru geomet-0.2.1.post1/geomet/geopackage.py geomet-1.0.0/geomet/geopackage.py --- geomet-0.2.1.post1/geomet/geopackage.py 1970-01-01 00:00:00.000000000 +0000 +++ geomet-1.0.0/geomet/geopackage.py 2022-11-12 11:31:55.000000000 +0000 @@ -0,0 +1,417 @@ +# Copyright 2020 Tom Caruso & individual contributors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import struct as _struct + +from geomet.util import ( + as_bin_str as _as_bin_str, + take as _take, + endian_token as _endian_token +) +from geomet import wkb as _wkb + + +def dump(obj, dest_file, big_endian=True): + """ + Dump GeoJSON-like `dict` to GeoPackage binary + and write it to the `dest_file`. + + :param dict obj: + A GeoJSON-like dictionary. It must at least the keys 'type' and + 'coordinates'. + :param dest_file: + Open and writable file-like object. + :param bool big_endian: + specify endianess of the dumped object. + + :return: + """ + dest_file.write(dumps(obj, big_endian)) + + +def load(source_file): + """ + Load a GeoJSON `dict` object from a ``source_file`` containing + GeoPackage (as a byte string). + + :param source_file: + Open and readable file-like object. + + :return: + A GeoJSON `dict` representing the geometry read from the file. + """ + return loads(source_file.read()) + + +def dumps(obj, big_endian=True): + """ + Dump a GeoJSON-like dict to a GeoPackage bytestring. + + + If the dict contains a top-level 'meta' key like so: + + ``` + 'meta': {'srid': 4326} + ``` + then the srid will be added to the geopackage header, but *not* + to the WKB geometry header. + + + If the dict contains a top-level 'bbox' key like so: + + ``` + 'bbox': [0, 0, 3, 3] + ``` + + Then an envelope will be added to the geopackage header + with this information. + + + If the geometry's coordinates are empty (an empty list) + then the geopackage header's "empty" flag will be set, + denoting that this geometry has no coordinates. + + Please note that while this library can parse geopackages + with a mixed byte-order in the header, it will only produce + blobs with consistent byte order (albeit properly marked as such). + That means you cannot product a geopackage with e.g. little-endian + header and big-endian WKB geometry. + + :param dict obj: + The geojson geometry to dump + :param bool big_endian: + if True, the geopackage binary will use big-endian + byte order, little-endian otherwise. + + :return bytes: + bytestring representing the geometry in geopackage + format. + """ + header = _build_geopackage_header(obj, not big_endian) + result = _wkb._dumps(obj, big_endian, include_meta=False) + return header + result + + +def loads(string): + """ + Construct a GeoJSON `dict` from geopackage (string). + + This function strips the geopackage header from the + string and passes the remaining WKB geometry to the + `geomet.wkb.loads` function. + + The envelope, if present, is added to the GeoJSON as + a key called 'bbox' as per the GeoJSON spec, [1]. + + If an SRID is specified in the geopackage header + AND the wkb header, the SRID in the geopackage header + will take precedence and will replace that SRID + in the returned dict. + + [1] https://tools.ietf.org/html/rfc7946#section-5 + + :param bytes string: + geopackage byte string. + :return dict: + GeoJSON represented the parsed geopackage binary. + """ + string = iter(string) + + header = _as_bin_str(_take(_GeoPackage.HEADER_LEN, string)) + + _check_is_valid(header) + g, p, version, empty, envelope_indicator, is_little_endian, srid = ( + _parse_header(header) + ) + + wkb_offset = _get_wkb_offset(envelope_indicator) + left_to_take = (wkb_offset - _GeoPackage.HEADER_LEN) + envelope_data = _as_bin_str(_take(left_to_take, string)) + + if envelope_data: + envelope = _parse_envelope( + envelope_indicator, envelope_data, is_little_endian + ) + + result = _wkb.loads(string) + + if srid: + result['meta'] = {'srid': int(srid)} + result['crs'] = { + 'type': 'name', + 'properties': {'name': 'EPSG%s' % srid}, + } + + if envelope_data: + result['bbox'] = envelope + + return result + + +class _GeoPackage: + """ + Much more information on geopackage structure + can be found here: http://www.geopackage.org/spec/#gpb_format + """ + # The ascii letter 'G' + MAGIC1 = 0x47 + # The ascii letter 'P' + MAGIC2 = 0x50 + VERSION1 = 0x00 + HEADER_LEN = 8 + HEADER_PACK_FMT = "BBBBI" + ENVELOPE_2D_LEN = 32 + ENVELOPE_3D_LEN = 48 + ENVELOPE_4D_LEN = 64 + ENVELOPE_MASK = 0b00001111 + EMPTY_GEOM_MASK = 0b00011111 + ENDIANNESS_MASK = 0b00000001 + + +# map the "envelope indicator" integer we get out of the geopackage header +# to the dimensionality of the envelope. +# more info here: http://www.geopackage.org/spec/#gpb_format +# in the "flags" section, bits 3, 2, 1. +_indicator_to_dim = { + 0: 0, + 1: 4, + 2: 6, + 3: 6, + 4: 8, +} + +# Map the dimensionality of our envelope to the indicator +# integer we will use in the geopackage binary header. +# because we have no way to tell between Z and M values, +# if the geometry has 3 dimensions we default to assume Z. +_dim_to_indicator = { + 0: 0, + 4: 1, + 6: 2, + 8: 4 +} + + +def is_valid(data): + """ + Check if the data represents a valid geopackage + geometry. Input can be either the full geometry or + just the header. + + :param bytes data: + bytes representing the geopackage binary. + + :return (bool, str): + Is the geopackage valid, if not, string describing why + """ + g, p, version, _, envelope_indicator, _, _ = _parse_header(data[:8]) + if (g != _GeoPackage.MAGIC1) or (p != _GeoPackage.MAGIC2): + return False, "Missing Geopackage header magic bytes" + if version != _GeoPackage.VERSION1: + return False, "Geopackage version must be 0" + if (envelope_indicator < 0) or (envelope_indicator > 4): + return False, "Envelope indicator must be between 0-4" + return True, "" + + +def _header_is_little_endian(header): + """ + Check to see if the header is encoded + as little endian or big endian. + + Either the entire binary blob or + just the header can be passed in. + + :param bytes header: + geopackage header or binary blob + + :return bool: is the header little endian + """ + (flags,) = _struct.unpack("B", header[3:4]) + return flags & _GeoPackage.ENDIANNESS_MASK + + +def _parse_header(header): + """ + Unpack all information from the geopackage + header, including "magic" GP bytes. Returns + all of them so we can confirm that this + geopackage is validly formed. Can also accept + the full binary blob. + + :param header: + the header or the full geometry. + + :return 7-tuple: + all attributes stored in the binary header. + """ + is_little_endian = _header_is_little_endian(header) + fmt = _endian_token(is_little_endian) + _GeoPackage.HEADER_PACK_FMT + + g, p, version, flags, srid = _struct.unpack( + fmt, header[:_GeoPackage.HEADER_LEN] + ) + empty, envelope_indicator, endianness = _parse_flags(flags) + return g, p, version, empty, envelope_indicator, endianness, srid + + +def _parse_flags(flags): + """ + Parse the bits in the "flags" byte + of the geopackage header to retrieve + useful information. We specifically parse + the endianness, the envelope indicator, + and the "empty" flag. + + Much more info can be found in + the documentation [1]. + + [1] http://www.geopackage.org/spec/#gpb_format + :param byte flags: + The "flags" byte of a geopackage header. + + :return tuple: + """ + endianness = flags & _GeoPackage.ENDIANNESS_MASK + envelope_indicator = (flags & _GeoPackage.ENVELOPE_MASK) >> 1 + empty = (flags & _GeoPackage.EMPTY_GEOM_MASK) >> 4 + return empty, envelope_indicator, endianness + + +def _build_flags(empty, envelope_indicator, is_little_endian=1): + """ + Create the "flags" byte which goes into + the geopackage header. Much more info + can be found in the documentation [1]. + + [1] http://www.geopackage.org/spec/#gpb_format + + :param int empty: + 0 or 1 indicating whether the geometry is empty. + True and False also work as expected. + :param int envelope_indicator: + indicates the dimensionality of the envelope. + :param int is_little_endian: + 0 or 1 (or False / True) indicating + whether the header should be + little-endian encoded. + + :return byte: + geopackage header flags + """ + flags = 0b0 + if empty: + flags = (flags | 1) << 3 + if envelope_indicator: + flags = flags | envelope_indicator + + return (flags << 1) | is_little_endian + + +def _build_geopackage_header(obj, is_little_endian): + """ + Create the geopackage header for the input object. + Looks for a 'bbox' key on the geometry to use + for an envelope, and a 'meta' key with an + SRID to encode into the header. + + :param dict obj: + a geojson object + :param bool is_little_endian: + which endianness to use when + encoding the data. + + :return bytes: geopackage header. + """ + # Collect geometry metadata. + empty = 1 if len(obj['coordinates']) == 0 else 0 + envelope = obj.get('bbox', []) + srid = obj.get('meta', {}).get('srid', 0) + + try: + envelope_indicator = _dim_to_indicator[len(envelope)] + except KeyError: + raise ValueError("Bounding box must be of length 2*n where " + "n is the number of dimensions represented " + "in the contained geometries.") + + pack_args = [ + _GeoPackage.MAGIC1, + _GeoPackage.MAGIC2, + _GeoPackage.VERSION1, + # This looks funny, but _build_flags wants a 1 or 0 for + # "little endian" because it uses it to `or` with the bits. + # Conveniently, in Python, False == 0 and True == 1, so + # we can pass the boolean right in and it works as expected. + _build_flags(empty, envelope_indicator, is_little_endian), + srid + ] + + pack_fmt = _endian_token(is_little_endian) + _GeoPackage.HEADER_PACK_FMT + + # This has no effect if we have a 0 envelope indicator. + pack_fmt += ('d' * _indicator_to_dim[envelope_indicator]) + pack_args.extend(envelope) + + return _struct.pack(pack_fmt, *pack_args) + + +def _check_is_valid(data): + """ + Raise if the header is not valid geopackage. + + :param bytes data: Geopackage data or header. + + :return None: + """ + valid, reason = is_valid(data) + if not valid: + raise ValueError("Could not read Geopackage geometry " + "because of errors: " + reason) + + +def _get_wkb_offset(envelope_indicator): + """ + Get the full byte offset at which the WKB geometry lies + in the geopackage geometry. + + :param int envelope_indicator: + indicates the dimensionality of the envelope. + + :return int: + number of bytes until the beginning of the + WKB geometry. + + """ + base_len = _GeoPackage.HEADER_LEN + return (base_len * _indicator_to_dim[envelope_indicator]) + base_len + + +def _parse_envelope(envelope_indicator, envelope, is_little_endian): + """ + Parse a geopackage envelope bytestring into an n-tuple + of floats. + + :param int envelope_indicator: + indicates the dimensionality of the envelope. + :param bytes envelope: + Bytestring of the envelope values. + :param bool is_little_endian: + how to pack the bytes in the envelope. + + :return tuple[float]: Geometry envelope. + """ + pack_fmt = _endian_token(is_little_endian) + pack_fmt += ('d' * _indicator_to_dim[envelope_indicator]) + return _struct.unpack(pack_fmt, envelope) diff -Nru geomet-0.2.1.post1/geomet/__init__.py geomet-1.0.0/geomet/__init__.py --- geomet-0.2.1.post1/geomet/__init__.py 2020-01-27 11:00:26.000000000 +0000 +++ geomet-1.0.0/geomet/__init__.py 2022-11-12 11:31:55.000000000 +0000 @@ -11,7 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -__version__ = '0.2.1-1' +__version__ = '1.0.0' class InvalidGeoJSONException(Exception): diff -Nru geomet-0.2.1.post1/geomet/util.py geomet-1.0.0/geomet/util.py --- geomet-0.2.1.post1/geomet/util.py 2020-01-27 11:00:26.000000000 +0000 +++ geomet-1.0.0/geomet/util.py 2022-11-12 11:31:55.000000000 +0000 @@ -13,10 +13,7 @@ # limitations under the License. import itertools import six -if six.PY2: - import collections -else: - import collections.abc as collections +import collections.abc as collections def block_splitter(data, block_size): @@ -71,10 +68,7 @@ def as_bin_str(a_list): - if six.PY2: - return b''.join(a_list) - else: - return bytes(a_list) + return bytes(a_list) def round_geom(geom, precision=None): @@ -126,3 +120,10 @@ yield y else: yield x + + +def endian_token(is_little_endian): + if is_little_endian: + return '<' + else: + return '>' diff -Nru geomet-0.2.1.post1/geomet/wkb.py geomet-1.0.0/geomet/wkb.py --- geomet-0.2.1.post1/geomet/wkb.py 2020-01-27 11:00:26.000000000 +0000 +++ geomet-1.0.0/geomet/wkb.py 2022-11-12 11:31:55.000000000 +0000 @@ -12,7 +12,6 @@ # See the License for the specific language governing permissions and # limitations under the License. import binascii -import six import struct from geomet.util import block_splitter @@ -116,7 +115,7 @@ since this is big endian byte order), indicated as 0x20. If the SRID flag is not set, the high byte will always be null (0x00). :returns: - 3-tuple ofGeoJSON geometry type label, the bytes resprenting the + 3-tuple ofGeoJSON geometry type label, the bytes representing the geometry type, and a separate "has SRID" flag. If the input `type_bytes` contains an SRID flag, it will be removed. @@ -132,9 +131,7 @@ """ # slice off the high byte, which may contain the SRID flag - high_byte = type_bytes[0] - if six.PY3: - high_byte = bytes([high_byte]) + high_byte = bytes([type_bytes[0]]) has_srid = high_byte == b'\x20' if has_srid: # replace the high byte with a null byte @@ -213,22 +210,22 @@ Defaults to `True`. If `True`, data values in the generated WKB will be represented using big endian byte order. Else, little endian. - TODO: remove this - - :param str dims: - Indicates to WKB representation desired from converting the given - GeoJSON `dict` ``obj``. The accepted values are: - - * '2D': 2-dimensional geometry (X, Y) - * 'Z': 3-dimensional geometry (X, Y, Z) - * 'M': 3-dimensional geometry (X, Y, M) - * 'ZM': 4-dimensional geometry (X, Y, Z, M) - :returns: A WKB binary string representing of the ``obj``. """ + return _dumps(obj, big_endian) + + +def _dumps(obj, big_endian=True, include_meta=True): + """ + Basically perform the action of dumps, but with some extra flags for + behavior specifically needed by the geopackage...package. + """ geom_type = obj['type'] - meta = obj.get('meta', {}) + if include_meta: + meta = obj.get('meta', {}) + else: + meta = {} exporter = _dumps_registry.get(geom_type) if exporter is None: @@ -694,10 +691,7 @@ verts_wkb = as_bin_str(take(8 * num_verts * num_dims, data_bytes)) verts = block_splitter(verts_wkb, 8) - if six.PY2: - verts = (b''.join(x) for x in verts) - elif six.PY3: - verts = (b''.join(bytes([y]) for y in x) for x in verts) + verts = (b''.join(bytes([y]) for y in x) for x in verts) for vert_wkb in block_splitter(verts, num_dims): values = [struct.unpack('%sd' % endian_token, x)[0] for x in vert_wkb] @@ -922,7 +916,7 @@ _dumps_registry = { - 'Point': _dump_point, + 'Point': _dump_point, 'LineString': _dump_linestring, 'Polygon': _dump_polygon, 'MultiPoint': _dump_multipoint, diff -Nru geomet-0.2.1.post1/geomet/wkt.py geomet-1.0.0/geomet/wkt.py --- geomet-0.2.1.post1/geomet/wkt.py 2020-01-27 11:00:26.000000000 +0000 +++ geomet-1.0.0/geomet/wkt.py 2022-11-12 11:31:55.000000000 +0000 @@ -195,7 +195,12 @@ # if get a `decimals` value of 0, we want to return an int. return repr(int(round(value, decimals))) - rounded = repr(round(value, decimals)) + rounded = round(value, decimals) + + if 'e' in repr(rounded): + rounded = format(rounded, '.{}f'.format(decimals)) + else: + rounded = repr(rounded) rounded += '0' * (decimals - len(rounded.split('.')[1])) return rounded @@ -214,9 +219,15 @@ WKT representation of the input GeoJSON Point ``obj``. """ coords = obj['coordinates'] - pt = 'POINT (%s)' % ' '.join(_round_and_pad(c, decimals) - for c in coords) - return pt + + if not coords: + fmt = 'EMPTY' + else: + fmt = '(%s)' % ( + ' '.join(_round_and_pad(c, decimals) for c in coords) + ) + + return 'POINT %s' % fmt def _dump_linestring(obj, decimals): @@ -227,10 +238,18 @@ :func:`_dump_point`. """ coords = obj['coordinates'] - ls = 'LINESTRING (%s)' - ls %= ', '.join(' '.join(_round_and_pad(c, decimals) - for c in pt) for pt in coords) - return ls + + if not coords: + fmt = 'EMPTY' + else: + fmt = '(%s)' % ( + ', '.join( + ' '.join(_round_and_pad(c, decimals) for c in pt) + for pt in coords + ) + ) + + return 'LINESTRING %s' % fmt def _dump_polygon(obj, decimals): @@ -241,13 +260,17 @@ :func:`_dump_point`. """ coords = obj['coordinates'] - poly = 'POLYGON (%s)' - rings = (', '.join(' '.join(_round_and_pad(c, decimals) - for c in pt) for pt in ring) - for ring in coords) - rings = ('(%s)' % r for r in rings) - poly %= ', '.join(rings) - return poly + + if not coords: + fmt = 'EMPTY' + else: + rings = (', '.join(' '.join(_round_and_pad(c, decimals) + for c in pt) for pt in ring) + for ring in coords) + + fmt = '(%s)' % ', '.join('(%s)' % r for r in rings) + + return 'POLYGON %s' % fmt def _dump_multipoint(obj, decimals): @@ -258,13 +281,16 @@ :func:`_dump_point`. """ coords = obj['coordinates'] - mp = 'MULTIPOINT (%s)' - points = (' '.join(_round_and_pad(c, decimals) - for c in pt) for pt in coords) - # Add parens around each point. - points = ('(%s)' % pt for pt in points) - mp %= ', '.join(points) - return mp + + if not coords: + fmt = "EMPTY" + else: + points = (' '.join(_round_and_pad(c, decimals) + for c in pt) for pt in coords) + # Add parens around each point. + fmt = '(%s)' % ', '.join('(%s)' % pt for pt in points) + + return 'MULTIPOINT %s' % fmt def _dump_multilinestring(obj, decimals): @@ -275,11 +301,22 @@ :func:`_dump_point`. """ coords = obj['coordinates'] - mlls = 'MULTILINESTRING (%s)' - linestrs = ('(%s)' % ', '.join(' '.join(_round_and_pad(c, decimals) - for c in pt) for pt in linestr) for linestr in coords) - mlls %= ', '.join(ls for ls in linestrs) - return mlls + + if not coords: + fmt = 'EMPTY' + else: + linestrs = ( + '(%s)' % + ', '.join( + ' '.join( + _round_and_pad( + c, + decimals) for c in pt + ) for pt in linestr) for linestr in coords) + + fmt = '(%s)' % ', '.join(ls for ls in linestrs) + + return 'MULTILINESTRING %s' % fmt def _dump_multipolygon(obj, decimals): @@ -290,25 +327,26 @@ :func:`_dump_point`. """ coords = obj['coordinates'] - mp = 'MULTIPOLYGON (%s)' - - polys = ( - # join the polygons in the multipolygon - ', '.join( - # join the rings in a polygon, - # and wrap in parens - '(%s)' % ', '.join( - # join the points in a ring, + if not coords: + fmt = 'EMPTY' + else: + fmt = '(%s)' % ( + # join the polygons in the multipolygon + ', '.join( + # join the rings in a polygon, # and wrap in parens '(%s)' % ', '.join( - # join coordinate values of a vertex - ' '.join(_round_and_pad(c, decimals) for c in pt) - for pt in ring) - for ring in poly) - for poly in coords) - ) - mp %= polys - return mp + # join the points in a ring, + # and wrap in parens + '(%s)' % ', '.join( + # join coordinate values of a vertex + ' '.join(_round_and_pad(c, decimals) for c in pt) + for pt in ring) + for ring in poly) + for poly in coords) + ) + + return 'MULTIPOLYGON %s' % fmt def _dump_geometrycollection(obj, decimals): @@ -321,23 +359,25 @@ The WKT conversions for each geometry in the collection are delegated to their respective functions. """ - gc = 'GEOMETRYCOLLECTION (%s)' geoms = obj['geometries'] - geoms_wkt = [] - for geom in geoms: - geom_type = geom['type'] - geoms_wkt.append(_dumps_registry.get(geom_type)(geom, decimals)) - gc %= ','.join(geoms_wkt) - return gc + if not geoms: + fmt = 'EMPTY' + else: + geoms_wkt = [] + for geom in geoms: + geom_type = geom['type'] + geoms_wkt.append(_dumps_registry.get(geom_type)(geom, decimals)) + fmt = '(%s)' % ','.join(geoms_wkt) + return 'GEOMETRYCOLLECTION %s' % fmt def _load_point(tokens, string): """ :param tokens: - A generator of string tokens for the input WKT, begining just after the - geometry type. The geometry type is consumed before we get to here. For - example, if :func:`loads` is called with the input 'POINT(0.0 1.0)', - ``tokens`` would generate the following values: + A generator of string tokens for the input WKT, beginning just after + the geometry type. The geometry type is consumed before we get to + here. For example, if :func:`loads` is called with the input + 'POINT(0.0 1.0)', ``tokens`` would generate the following values: .. code-block:: python ['(', '0.0', '1.0', ')'] @@ -347,7 +387,11 @@ :returns: A GeoJSON `dict` Point representation of the WKT ``string``. """ - if not next(tokens) == '(': + next_token = next(tokens) + + if next_token == 'EMPTY': + return dict(type='Point', coordinates=[]) + elif not next_token == '(': raise ValueError(INVALID_WKT_FMT % string) coords = [] @@ -371,7 +415,11 @@ :returns: A GeoJSON `dict` LineString representation of the WKT ``string``. """ - if not next(tokens) == '(': + next_token = next(tokens) + + if next_token == 'EMPTY': + return dict(type='LineString', coordinates=[]) + elif not next_token == '(': raise ValueError(INVALID_WKT_FMT % string) # a list of lists @@ -403,7 +451,11 @@ :returns: A GeoJSON `dict` Polygon representation of the WKT ``string``. """ - open_parens = next(tokens), next(tokens) + next_token = next(tokens) + if next_token == 'EMPTY': + return dict(type='Polygon', coordinates=[]) + + open_parens = next(tokens), next_token if not open_parens == ('(', '('): raise ValueError(INVALID_WKT_FMT % string) @@ -454,8 +506,11 @@ :returns: A GeoJSON `dict` MultiPoint representation of the WKT ``string``. """ - open_paren = next(tokens) - if not open_paren == '(': + next_token = next(tokens) + + if next_token == 'EMPTY': + return dict(type='MultiPoint', coordinates=[]) + elif not next_token == '(': raise ValueError(INVALID_WKT_FMT % string) coords = [] @@ -470,8 +525,6 @@ paren_depth -= 1 if paren_depth == 0: break - elif t == '': - pass elif t == ',': # the point is done coords.append(pt) @@ -497,8 +550,11 @@ :returns: A GeoJSON `dict` MultiPolygon representation of the WKT ``string``. """ - open_paren = next(tokens) - if not open_paren == '(': + next_token = next(tokens) + + if next_token == 'EMPTY': + return dict(type='MultiPolygon', coordinates=[]) + elif not next_token == '(': raise ValueError(INVALID_WKT_FMT % string) polygons = [] @@ -510,7 +566,7 @@ if t == ')': # we're done; no more polygons. break - except StopIteration: + except (StopIteration, tokenize.TokenError): # If we reach this, the WKT is not valid. raise ValueError(INVALID_WKT_FMT % string) @@ -525,8 +581,11 @@ :returns: A GeoJSON `dict` MultiLineString representation of the WKT ``string``. """ - open_paren = next(tokens) - if not open_paren == '(': + next_token = next(tokens) + + if next_token == 'EMPTY': + return dict(type='MultiLineString', coordinates=[]) + elif not next_token == '(': raise ValueError(INVALID_WKT_FMT % string) linestrs = [] @@ -538,7 +597,7 @@ if t == ')': # we're done; no more linestrings. break - except StopIteration: + except (StopIteration, tokenize.TokenError): # If we reach this, the WKT is not valid. raise ValueError(INVALID_WKT_FMT % string) @@ -556,8 +615,11 @@ A GeoJSON `dict` GeometryCollection representation of the WKT ``string``. """ - open_paren = next(tokens) - if not open_paren == '(': + next_token = next(tokens) + + if next_token == 'EMPTY': + return dict(type='GeometryCollection', geometries=[]) + elif not next_token == '(': raise ValueError(INVALID_WKT_FMT % string) geoms = [] @@ -575,13 +637,13 @@ load_func = _loads_registry.get(geom_type) geom = load_func(tokens, string) geoms.append(geom) - except StopIteration: + except (StopIteration, tokenize.TokenError): raise ValueError(INVALID_WKT_FMT % string) return result _dumps_registry = { - 'Point': _dump_point, + 'Point': _dump_point, 'LineString': _dump_linestring, 'Polygon': _dump_polygon, 'MultiPoint': _dump_multipoint, diff -Nru geomet-0.2.1.post1/geomet.egg-info/PKG-INFO geomet-1.0.0/geomet.egg-info/PKG-INFO --- geomet-0.2.1.post1/geomet.egg-info/PKG-INFO 2020-01-27 11:00:26.000000000 +0000 +++ geomet-1.0.0/geomet.egg-info/PKG-INFO 2022-11-12 11:32:59.000000000 +0000 @@ -1,28 +1,31 @@ -Metadata-Version: 1.2 +Metadata-Version: 2.1 Name: geomet -Version: 0.2.1.post1 -Summary: GeoJSON <-> WKT/WKB conversion utilities +Version: 1.0.0 +Summary: Conversion library for common geospatial data formats (GeoJSON/WKT/EWKT/WKB/EWKB/GeoPackage/EsriJson) Home-page: https://github.com/geomet/geomet Maintainer: Lars Butler Maintainer-email: lars.butler@gmail.com License: Apache 2.0 -Description: - GeoMet - - Convert GeoJSON to WKT/WKB (Well-Known Text/Binary), and vice versa. - -Keywords: wkb wkt geojson +Keywords: esri,ewkb,ewkt,geojson,geopackage,geospatial,gis,spatial,wkb,wkt Platform: any -Classifier: Development Status :: 4 - Beta +Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: Apache Software License Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Education Classifier: Intended Audience :: Science/Research Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Topic :: Scientific/Engineering :: GIS -Requires-Python: >2.6, !=3.3.*, <4 +Requires-Python: >=3.7, <4 +License-File: LICENSE +License-File: AUTHORS.txt + + +GeoMet + +Convert GeoJSON to WKT/WKB (Well-Known Text/Binary), and vice versa. + + diff -Nru geomet-0.2.1.post1/geomet.egg-info/SOURCES.txt geomet-1.0.0/geomet.egg-info/SOURCES.txt --- geomet-0.2.1.post1/geomet.egg-info/SOURCES.txt 2020-01-27 11:00:26.000000000 +0000 +++ geomet-1.0.0/geomet.egg-info/SOURCES.txt 2022-11-12 11:32:59.000000000 +0000 @@ -2,12 +2,9 @@ LICENSE README.md setup.py -./geomet/__init__.py -./geomet/tool.py -./geomet/util.py -./geomet/wkb.py -./geomet/wkt.py geomet/__init__.py +geomet/esri.py +geomet/geopackage.py geomet/tool.py geomet/util.py geomet/wkb.py diff -Nru geomet-0.2.1.post1/PKG-INFO geomet-1.0.0/PKG-INFO --- geomet-0.2.1.post1/PKG-INFO 2020-01-27 11:00:26.000000000 +0000 +++ geomet-1.0.0/PKG-INFO 2022-11-12 11:32:59.000000000 +0000 @@ -1,28 +1,31 @@ -Metadata-Version: 1.2 +Metadata-Version: 2.1 Name: geomet -Version: 0.2.1.post1 -Summary: GeoJSON <-> WKT/WKB conversion utilities +Version: 1.0.0 +Summary: Conversion library for common geospatial data formats (GeoJSON/WKT/EWKT/WKB/EWKB/GeoPackage/EsriJson) Home-page: https://github.com/geomet/geomet Maintainer: Lars Butler Maintainer-email: lars.butler@gmail.com License: Apache 2.0 -Description: - GeoMet - - Convert GeoJSON to WKT/WKB (Well-Known Text/Binary), and vice versa. - -Keywords: wkb wkt geojson +Keywords: esri,ewkb,ewkt,geojson,geopackage,geospatial,gis,spatial,wkb,wkt Platform: any -Classifier: Development Status :: 4 - Beta +Classifier: Development Status :: 5 - Production/Stable Classifier: License :: OSI Approved :: Apache Software License Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Education Classifier: Intended Audience :: Science/Research Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Programming Language :: Python :: 3.5 -Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 Classifier: Topic :: Scientific/Engineering :: GIS -Requires-Python: >2.6, !=3.3.*, <4 +Requires-Python: >=3.7, <4 +License-File: LICENSE +License-File: AUTHORS.txt + + +GeoMet + +Convert GeoJSON to WKT/WKB (Well-Known Text/Binary), and vice versa. + + diff -Nru geomet-0.2.1.post1/README.md geomet-1.0.0/README.md --- geomet-0.2.1.post1/README.md 2020-01-27 11:00:26.000000000 +0000 +++ geomet-1.0.0/README.md 2022-11-12 11:31:55.000000000 +0000 @@ -1,56 +1,38 @@ -GeoMet [![Build Status](https://secure.travis-ci.org/geomet/geomet.png?branch=master)](http://travis-ci.org/geomet/geomet) -====== +# GeoMet [![geomet](https://circleci.com/gh/geomet/geomet.svg?style=shield)](https://app.circleci.com/pipelines/github/geomet) -Convert [GeoJSON](http://www.geojson.org/geojson-spec.html) to -[WKT/WKB](http://en.wikipedia.org/wiki/Well-known_text) (Well-Known -Text/Binary), and vice versa. [Extended WKB/WKT](https://postgis.net/docs/using_postgis_dbmanagement.html#EWKB_EWKT) -are also supported. Conversion functions are exposed through -idiomatic `load/loads/dump/dumps` interfaces. +Pure-Python conversion library for common geospatial data formats. +Supported formats include: +- [GeoJSON](http://www.geojson.org/geojson-spec.html) +- [WKT/WKB](http://en.wikipedia.org/wiki/Well-known_text) (Well-Known Text/Binary) +- [Extended WKB/WKT](https://postgis.net/docs/using_postgis_dbmanagement.html#EWKB_EWKT) +- [GeoPackage Binary](http://www.geopackage.org/spec/#gpb_format) -The name "GeoMet" was inspired by "met", the German word for -[mead](http://en.wikipedia.org/wiki/Mead). It is also a shortened version of -the word "geometry". - -GeoMet is intended to cover all common use cases for dealing with 2D, 3D, and -4D geometries (including 'Z', 'M', and 'ZM'). -The following conversion functions are supported. +## Install -WKT/EWKT <--> GeoJSON: - -- Point -- LineString -- Polygon -- MultiPoint -- MultiLineString -- MultiPolygon -- GeometryCollection - -WKB/EWKB <--> GeoJSON: - -- Point -- LineString -- Polygon -- MultiPoint -- MultiLineString -- MultiPolygon -- GeometryCollection - -### Installation ### - -Install from [PyPI](https://pypi.python.org/pypi) (easiest method): +Install the latest version from [PyPI](https://pypi.org/project/geomet/): $ pip install geomet -Clone the source code from git and run: +## Functionality - $ python setup.py install +Converion functions are exposed through idiomatic `load/loads/dump/dumps` +interfaces. -You can also install directly from the git repo using pip: +GeoMet is intended to cover all common use cases for dealing with 2D, 3D, and +4D geometries (including 'Z', 'M', and 'ZM'). - $ pip install git+git://github.com/geomet/geomet.git +| Geometry | WKT/EWKT | WKB/EWKB | GeoPackage Binary | EsriJSON | +| -------- | :------: | :------: | :---------------: | :------: | +| Point | ✅ | ✅ | ✅| ✅ | +| LineString | ✅ | ✅ | ✅| ✅ | +| Polygon | ✅ | ✅ | ✅| ✅ | +| MultiPoint | ✅ | ✅ | ✅| ✅ | +| MultiLineString | ✅ | ✅ | ✅| ✅ | +| MultiPolygon | ✅ | ✅ | ✅| ✅ | +| GeometryCollection | ✅ | ✅ | ✅| ✅ | -### Example usage ### +## Example usage Coverting a 'Point' GeoJSON object to WKT: @@ -63,9 +45,18 @@ >>> from geomet import wkb >>> wkb.dumps(point) - '\x00\x00\x00\x10\x01@]\x19\x99\x99\x99\x99\x9a@F\x99\x99\x99\x99\x99\x9a@&333333' + b'\x00\x00\x00\x10\x01@]\x19\x99\x99\x99\x99\x9a@F\x99\x99\x99\x99\x99\x9a@&333333' >>> wkb.dumps(point, big_endian=False) - '\x01\x01\x10\x00\x00\x9a\x99\x99\x99\x99\x19]@\x9a\x99\x99\x99\x99\x99F@333333&@' + b'\x01\x01\x10\x00\x00\x9a\x99\x99\x99\x99\x19]@\x9a\x99\x99\x99\x99\x99F@333333&@' + +Converting a 'Point' GeoJSON object to GeoPackage Binary: + + >>> from geomet import geopackage + >>> geopackage.dumps(point) + b'GP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x03\xe9@]\x19\x99\x99\x99\x99\x9a@F\x99\x99\x99\x99\x99\x9a@&333333' + >>> geopackage.dumps(point, big_endian=False) + b'GP\x00\x01\x00\x00\x00\x00\x01\xe9\x03\x00\x00\x9a\x99\x99\x99\x99\x19]@\x9a\x99\x99\x99\x99\x99F@333333&@' + Converting a 'LineString' GeoJSON object to WKT: @@ -78,9 +69,16 @@ Converting a 'LineString' GeoJSON object to WKB: >>> wkb.dumps(linestring) - '\x00\x00\x00\x10\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00?\xf0\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@\x10\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00@>\x00\x00\x00\x00\x00\x00@\x14\x00\x00\x00\x00\x00\x00@\x10\x00\x00\x00\x00\x00\x00@D\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x10\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00?\xf0\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@\x10\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00@>\x00\x00\x00\x00\x00\x00@\x14\x00\x00\x00\x00\x00\x00@\x10\x00\x00\x00\x00\x00\x00@D\x00\x00\x00\x00\x00\x00' >>> wkb.dumps(linestring, big_endian=False) - '\x01\x02\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00\x10@\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00>@\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x00\x10@\x00\x00\x00\x00\x00\x00D@' + b'\x01\x02\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00\x10@\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00>@\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x00\x10@\x00\x00\x00\x00\x00\x00D@' + +Converting a 'LineString' GeoJSON object to GeoPackage Binary: + + >>> geopackage.dumps(linestring) + b'GP\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00?\xf0\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@\x10\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\x00@>\x00\x00\x00\x00\x00\x00@\x14\x00\x00\x00\x00\x00\x00@\x10\x00\x00\x00\x00\x00\x00@D\x00\x00\x00\x00\x00\x00' + >>> geopackage.dumps(linestring, big_endian=False) + b'GP\x00\x01\x00\x00\x00\x00\x01\x02\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00\xf0?\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00\x00\x10@\x00\x00\x00\x00\x00\x00\x00@\x00\x00\x00\x00\x00\x00>@\x00\x00\x00\x00\x00\x00\x14@\x00\x00\x00\x00\x00\x00\x10@\x00\x00\x00\x00\x00\x00D@' Converting 'Point' WKT to GeoJSON: @@ -92,7 +90,8 @@ >>> wkt.loads('GEOMETRYCOLLECTION(POINT(10 20),POLYGON(((0 0), (10 30), (30 10), (0 0)))') {'type': 'GeometryCollection', 'geometries': [{'type': 'Point', 'coordinates': [10.0, 20.0]}, {'type': 'Polygon', 'coordinates': [[[0.0, 0.0]], [[10.0, 30.0]], [[30.0, 10.0]], [[0.0, 0.0]]]}]} -EWKT/EWKB are also supported for all geometry types. This uses a custom extension +[EWKT/EWKB](http://postgis.net/documentation/manual-2.1/using_postgis_dbmanagement.html#EWKB_EWKT) +are also supported for all geometry types. This uses a custom extension to the GeoJSON standard in order to preserve SRID information through conversions. For example: @@ -105,8 +104,70 @@ >>> wkb.dumps({'type': 'Point', 'coordinates': [10.0, 20.0], 'meta': {'srid': '4326'}, 'crs': {'properties': {'name': 'EPSG4326'}, 'type': 'name'}}) '\x00 \x00\x00\x01\x00\x00\x10\xe6@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00' +GeoPackage binary supports encoding of SRID and envelope information. If your geopackage +has an envelope specified, then it will be added into the resulting GeoJSON in a key +called `'bbox'`: + + >>> gpkg = b'GP\x00\x03\x00\x00\x00\x00\xf0\x9e\xa0\xa7\x05;#@hZ\xbd\x93\x83GC@\xf0\x9e\xa0\xa7\x05;#@hZ\xbd\x93\x83GC@\x01\x01\x00\x00\x00\xf0\x9e\xa0\xa7\x05;#@hZ\xbd\x93\x83GC@' + >>> geopackage.loads(gpkg) + >>> {'type': 'Point', 'coordinates': [9.615277517659223, 38.55870291467437], 'bbox': (9.615277517659223, 38.55870291467437, 9.615277517659223, 38.55870291467437)} + +In the same way, if a 'bbox' key is present on a `dumps`-ed geometry, it will be added to the +header of the GeoPackage geometry: + + >>> polygon = {'type': 'Polygon', 'coordinates': [[[20.0, 20.0], [34.0, 124.0], [70.0, 140.0], [130.0, 130.0], [70.0, 100.0], [110.0, 70.0], [170.0, 20.0], [90.0, 10.0], [20.0, 20.0]]], 'bbox': (20.0, 170.0, 10.0, 140.0)} + >>> geopackage.dumps(polygon) + b'GP\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x004@\x00\x00\x00\x00\x00@e@\x00\x00\x00\x00\x00\x00$@\x00\x00\x00\x00\x00\x80a@\x00\x00\x00\x00\x03\x00\x00\x00\x01\x00\x00\x00\t@4\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@A\x00\x00\x00\x00\x00\x00@_\x00\x00\x00\x00\x00\x00@Q\x80\x00\x00\x00\x00\x00@a\x80\x00\x00\x00\x00\x00@`@\x00\x00\x00\x00\x00@`@\x00\x00\x00\x00\x00@Q\x80\x00\x00\x00\x00\x00@Y\x00\x00\x00\x00\x00\x00@[\x80\x00\x00\x00\x00\x00@Q\x80\x00\x00\x00\x00\x00@e@\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@V\x80\x00\x00\x00\x00\x00@$\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00@4\x00\x00\x00\x00\x00\x00' + +If an integer SRID identifier is present in a `'meta'` key (like `'meta': {'srid': 4326}`), then the SRID will be included in the +GeoPackage header. + +## History + +This library was originally created as the result of a bug report related +to another project: https://bugs.launchpad.net/openquake-old/+bug/1073909. +The source of this issue was largely due to a dependency on +[GEOS](https://libgeos.org/), which is written in C/C++. Depending on GEOS +requires any data conversion bug fixes to happen upstream, which takes time +and effort. Ultimately, this was the inspiration to create a more +lightweight, pure-Python conversion library as an alterntive tool for +reliably converting data between various geospatial formats. + +The name "GeoMet" was inspired by "met", the German word for +[mead](http://en.wikipedia.org/wiki/Mead). It is also a shortened version of +the word "geometry". + +## Limitations + +### Outputing "empty" geometries to binary formats is not supported + +Attempting to output an empty geometry to a binary format will result in an exception: `ValueError: Empty geometries cannot be represented in WKB. Reason: The dimensionality of the WKB would be ambiguous.` There are a few reasons for this this limitation: +- Any `EMTPY` geometry (e.g., `POINT EMPTY`, `MULTIPOLYGON EMPTY`, etc.) cannot be converted into binary format because binary formats such as WKB require an explicit dimension type (2d, Z, M, or ZM). This means that some objects cannot be reliably converted to and from different formats in a [bijective](https://en.wikipedia.org/wiki/Bijection) manner. +- The [GeoJSON standard](https://www.rfc-editor.org/rfc/rfc7946) does have a way of representing empty geometries; however, details are minimal and the dimensionality of such an object remains ambiguous. +- Representing some geometry types (such as points and lines) as "empty" is [deeply flawed to begin with](http://aleph0.clarku.edu/~djoyce/elements/bookI/defI1.html). For example, a point can represent any location in 2d, 3d, or 4d space. However, a point is infinitesimally small (it has no size) and it can't contain anything (it can't be "full"), therefore, it doesn't make sense for a point to be "empty". + +As a result, GeoMet has chosen to not attempt to address these problems, and +simply raise an exception instead. + +Example: + + >>> import geomet + >>> import geomet.wkt as wkt + >>> import geomet.wkb as wkb + >>> pt = wkt.loads('POINT EMPTY') + >>> pt + {'type': 'Point', 'coordinates': []} + >>> wkb.dumps(pt) + Traceback (most recent call last): + File "", line 1, in + File "/home/jdoe/geomet/geomet/wkb.py", line 216, in dumps + return _dumps(obj, big_endian) + File "/home/jdoe/geomet/geomet/wkb.py", line 238, in _dumps + raise ValueError( + ValueError: Empty geometries cannot be represented in WKB. Reason: The dimensionality of the WKB would be ambiguous. + -### See Also ### +## See also - [wellknown](https://github.com/mapbox/wellknown): A similar package for Node.js. - [geo](https://github.com/bryanjos/geo): A nearly-identical package for Elixir. diff -Nru geomet-0.2.1.post1/setup.py geomet-1.0.0/setup.py --- geomet-0.2.1.post1/setup.py 2020-01-27 11:00:26.000000000 +0000 +++ geomet-1.0.0/setup.py 2022-11-12 11:31:55.000000000 +0000 @@ -10,9 +10,6 @@ from setuptools import find_packages from setuptools import setup -if (3,2) < sys.version_info < (3,4): - raise RuntimeError("Python3.3 is no longer supported") - def get_version(): version_re = r"^__version__\s+=\s+['\"]([^'\"]*)['\"]" @@ -36,28 +33,38 @@ maintainer='Lars Butler', maintainer_email='lars.butler@gmail.com', url='https://github.com/geomet/geomet', - description='GeoJSON <-> WKT/WKB conversion utilities', + description='Conversion library for common geospatial data formats (GeoJSON/WKT/EWKT/WKB/EWKB/GeoPackage/EsriJson)', long_description=__doc__, platforms=['any'], packages=find_packages(exclude=['geomet.tests', 'geomet.tests.*']), entry_points={'console_scripts': ['geomet=geomet.tool:cli']}, license='Apache 2.0', - keywords='wkb wkt geojson', + keywords=[ + 'esri', + 'ewkb', + 'ewkt', + 'geojson', + 'geopackage', + 'geospatial', + 'gis', + 'spatial', + 'wkb', + 'wkt', + ], classifiers=[ - 'Development Status :: 4 - Beta', + 'Development Status :: 5 - Production/Stable', 'License :: OSI Approved :: Apache Software License', 'Intended Audience :: Developers', 'Intended Audience :: Education', 'Intended Audience :: Science/Research', 'Operating System :: OS Independent', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', 'Topic :: Scientific/Engineering :: GIS', ], zip_safe=False, install_requires=['click', 'six'], - python_requires=">2.6, !=3.3.*, <4", + python_requires=">=3.7, <4", )