fudgeo


Namefudgeo JSON
Version 0.7.0 PyPI version JSON
download
home_page
SummaryGeoPackage support from Python. fudgeo is a lightweight package for creating OGC GeoPackages, Feature Classes, and Tables. Easily read and write geometries and attributes to Feature Classes and Tables using regular Python objects and SQLite syntax.
upload_time2023-06-10 14:44:49
maintainer
docs_urlNone
author
requires_python>=3.7
licenseMIT License Copyright (c) 2021-2023 Integrated Informatics Inc Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
keywords geopackage
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # fudgeo

`fudgeo` removes the *fear uncertainty doubt* from using GeoPackages with 
`Python`. `fudgeo` is a lightweight package for creating OGC GeoPackages, Feature 
Classes, and Tables.  Easily read and write geometries and attributes to
Feature Classes and Tables using regular `Python` objects and `SQLite` syntax.

For details on OGC GeoPackages, please see the [OGC web page](http://www.geopackage.org/).


## Installation

`fudgeo` is available from the [Python Package Index](https://pypi.org/project/fudgeo/).


## Python Compatibility

The `fudgeo` library is compatible with Python 3.7 to 3.11.  Developed and 
tested on **macOS** and **Windows**, should be fine on **Linux** too.


## Usage

`fudgeo` can be used to: 
* Create a new empty `GeoPackage` or open an existing `GeoPackage`
* Create new `FeatureClass` or `Table` with optional overwrite
* Create `SpatialReferenceSystem` for a `FeatureClass`
* Build geometry objects from lists of coordinate values
* Work with data in `Table` or `FeatureClass` in a normal `SQLite` manner (e.g. `SELECT`, `INSERT`, `UPDATE`, `DELETE`)
* Retrieve fields from a `FeatureClass` or `Table`
* Access primary key field of `FeatureClass` or `Table` 
* Access geometry column name and geometry type for `FeatureClass`
* Add spatial index on `FeatureClass`
* Drop `FeatureClass` or `Table`


### Create an Empty GeoPackage / Open GeoPackage

```python
from fudgeo.geopkg import GeoPackage

# Creates an empty geopackage
gpkg: GeoPackage = GeoPackage.create('../data/example.gpkg')

# Opens an existing Geopackage (no validation)
gpkg: GeoPackage = GeoPackage('../data/example.gpkg')
```

`GeoPackage`s are created with *three* default Spatial References defined
automatically, a pair of Spatial References to handle **undefined** cases,
and a **WGS 84** entry. 

The definition of the WGS84 entry is flexible - meaning that the 
*WKT for WGS84* can be setup per the users liking. As an example, 
use with Esri's ArcGIS means either using the *EPSG WKT* or the *ESRI WKT*. By
default the *ESRI WKT* is used - However, if *EPSG WKT* is desired, you
may provide a ``flavor`` parameter to the create method specifying EPSG.

```python
from fudgeo.geopkg import GeoPackage

# Creates an empty geopackage using EPSG definitions
gpkg: GeoPackage = GeoPackage.create('../temp/test.gpkg', flavor='EPSG')
```

### Create a Feature Class

Use the `create_feature_class` method of a GeoPackage to make
a new feature class.  Feature classes require a name and a Spatial 
Reference, the name must follow SQLite naming requirements.  Each
feature class is defined with `fid` and `SHAPE` fields, additional
fields can be defined during creation.

A Feature Class can be created with *Z* or *M* (or both) enabled. If 
either of these options are enabled, the geometry inserted into the 
Feature Class **must** include a value for the option specified.

```python
from typing import Tuple
from fudgeo.enumeration import GeometryType, SQLFieldType
from fudgeo.geopkg import FeatureClass, Field, GeoPackage, SpatialReferenceSystem

SRS_WKT: str = (
    'PROJCS["WGS_1984_UTM_Zone_23N",'
    'GEOGCS["GCS_WGS_1984",'
    'DATUM["D_WGS_1984",'
    'SPHEROID["WGS_1984",6378137.0,298.257223563]],'
    'PRIMEM["Greenwich",0.0],'
    'UNIT["Degree",0.0174532925199433]],'
    'PROJECTION["Transverse_Mercator"],'
    'PARAMETER["False_Easting",500000.0],'
    'PARAMETER["False_Northing",0.0],'
    'PARAMETER["Central_Meridian",-45.0],'
    'PARAMETER["Scale_Factor",0.9996],'
    'PARAMETER["Latitude_Of_Origin",0.0],'
    'UNIT["Meter",1.0]]')

SRS: SpatialReferenceSystem = SpatialReferenceSystem(
    name='WGS_1984_UTM_Zone_23N', organization='EPSG',
    org_coord_sys_id=32623, definition=SRS_WKT)
fields: Tuple[Field, ...] = (
    Field('road_id', SQLFieldType.integer),
    Field('name', SQLFieldType.text, size=100),
    Field('begin_easting', SQLFieldType.double),
    Field('begin_northing', SQLFieldType.double),
    Field('end_easting', SQLFieldType.double),
    Field('end_northing', SQLFieldType.double),
    Field('begin_longitude', SQLFieldType.double),
    Field('begin_latitude', SQLFieldType.double),
    Field('end_longitude', SQLFieldType.double),
    Field('end_latitude', SQLFieldType.double),
    Field('is_one_way', SQLFieldType.boolean))

gpkg: GeoPackage = GeoPackage.create('../temp/test.gpkg')
fc: FeatureClass = gpkg.create_feature_class(
    'road_l', srs=SRS, fields=fields, shape_type=GeometryType.linestring,
    m_enabled=True, overwrite=True, spatial_index=True)
```

### About Spatial References For GeoPackages

Spatial References in GeoPackages can use any definition from any 
authority - be that `EPSG`, `ESRI`, or another authority. `fudgeo` imposes no 
restriction and performs no checks on the definitions provided. Take care 
to ensure that the definitions are compatible with the platform / software 
you intend to utilize with the `GeoPackage`.

### Insert Features into a Feature Class (SQL)

Features can be inserted into a Feature Class using SQL.

This example shows the creation of a random point Feature Class and
builds upon the code from previous examples. Note that the create Feature Class
portion of the code is omitted...

```python
from random import choice, randint
from string import ascii_uppercase, digits
from typing import List, Tuple

from fudgeo.geometry import LineStringM
from fudgeo.geopkg import GeoPackage

# Generate some random points and attributes
rows: List[Tuple[LineStringM, int, str, float, float, float, float, bool]] = []
for i in range(10000):
    name = ''.join(choice(ascii_uppercase + digits) for _ in range(10))
    road_id = randint(0, 1000)
    eastings = [randint(300000, 600000) for _ in range(20)]
    northings = [randint(1, 100000) for _ in range(20)]
    coords = [(x, y, m) for m, (x, y) in enumerate(zip(eastings, northings))]
    road = LineStringM(coords, srs_id=32623)
    rows.append((road, road_id, name, eastings[0], northings[0],
                 eastings[-1], northings[-1], False))

# NOTE Builds from previous examples
gpkg: GeoPackage = GeoPackage('../data/example.gpkg')   
with gpkg.connection as conn:
    conn.executemany("""
        INSERT INTO road_l (SHAPE, road_id, name, begin_easting, begin_northing, 
                            end_easting, end_northing, is_one_way) 
           VALUES (?, ?, ?, ?, ?, ?, ?, ?)""", rows)
```

### Geometry Examples

Review the tests for `fudgeo` for a comprehensive look into 
creating geometries, below are some examples showing the simplicity
of this package.


```python
from typing import List, Tuple
from fudgeo.geometry import LineStringZM, Point, Polygon

# Point in WGS 84
pt: Point = Point(x=-119, y=34)

# Line with ZM Values for use with UTM Zone 23N (WGS 84)
coords: List[Tuple[float, float, float, float]] = [
    (300000, 1, 10, 0), (300000, 4000000, 20, 1000),
    (700000, 4000000, 30, 2000), (700000, 1, 40, 3000)]
line: LineStringZM = LineStringZM(coords, srs_id=32623)

# list of rings where a ring is simply the list of points it contains.
rings: List[List[Tuple[float, float]]] = [
    [(300000, 1), (300000, 4000000), (700000, 4000000), (700000, 1), (300000, 1)]]
poly: Polygon = Polygon(rings, srs_id=32623)
```

### Select Features from GeoPackage (SQL)

When selecting features from a GeoPackage feature class use SQL.  For 
the most part (mainly simple geometries e.g. those without *Z* or *M*) this 
can be done via a basic `SELECT` statement like:

```python
gpkg = GeoPackage(...)
cursor = gpkg.connection.execute("""SELECT SHAPE, example_id FROM point_fc""")
features = cursor.fetchall()
```

This will return a list of tuples where each tuple contains a `Point`
object and an integer for `example_id` field.

When working with extended geometry types (those with *Z* and/or *M*) 
then the approach is to ensure `SQLite` knows how to convert the 
geopackage stored geometry to a `fudgeo` geometry, this is done like so:

```python
from typing import List, Tuple
from fudgeo.geometry import LineStringM
from fudgeo.geopkg import GeoPackage

gpkg: GeoPackage = GeoPackage('../data/example.gpkg')
cursor = gpkg.connection.execute(
    """SELECT SHAPE "[LineStringM]", road_id FROM test""")
features: List[Tuple[LineStringM, int]] = cursor.fetchall()
```

or a little more general, accounting for extended geometry types and
possibility of the geometry column being something other tha `SHAPE`:

```python
from typing import List, Tuple
from fudgeo.geometry import LineStringM
from fudgeo.geopkg import FeatureClass, GeoPackage

gpkg: GeoPackage = GeoPackage('../data/example.gpkg')
fc: FeatureClass = FeatureClass(geopackage=gpkg, name='road_l')
cursor = gpkg.connection.execute(f"""
    SELECT {fc.geometry_column_name} "[{fc.geometry_type}]", road_id 
    FROM {fc.escaped_name}""")
features: List[Tuple[LineStringM, int]] = cursor.fetchall()
```


## Extensions
### Spatial Index Extension
Spatial Index Extension implementation based on section [F.3. RTree Spatial Indexes](http://www.geopackage.org/spec131/index.html#extension_rtree)
of the **GeoPackage Encoding Standard**.

Spatial Indexes apply to individual feature classes.  A spatial index can be
added at create time or added on an existing feature class.

```python
from typing import Tuple
from fudgeo.enumeration import SQLFieldType
from fudgeo.geopkg import FeatureClass, Field, GeoPackage, SpatialReferenceSystem


SRS_WKT: str = (
    'PROJCS["WGS_1984_UTM_Zone_23N",'
    'GEOGCS["GCS_WGS_1984",'
    'DATUM["D_WGS_1984",'
    'SPHEROID["WGS_1984",6378137.0,298.257223563]],'
    'PRIMEM["Greenwich",0.0],'
    'UNIT["Degree",0.0174532925199433]],'
    'PROJECTION["Transverse_Mercator"],'
    'PARAMETER["False_Easting",500000.0],'
    'PARAMETER["False_Northing",0.0],'
    'PARAMETER["Central_Meridian",-45.0],'
    'PARAMETER["Scale_Factor",0.9996],'
    'PARAMETER["Latitude_Of_Origin",0.0],'
    'UNIT["Meter",1.0]]')
SRS: SpatialReferenceSystem = SpatialReferenceSystem(
    name='WGS_1984_UTM_Zone_23N', organization='EPSG',
    org_coord_sys_id=32623, definition=SRS_WKT)
fields: Tuple[Field, ...] = (
    Field('id', SQLFieldType.integer),
    Field('name', SQLFieldType.text, size=100))

gpkg: GeoPackage = GeoPackage.create('../temp/spatial_index.gpkg')
# add spatial index at create time
event: FeatureClass = gpkg.create_feature_class(
    'event_p', srs=SRS, fields=fields, spatial_index=True)
assert event.has_spatial_index is True

# add spatial index on an existing feature class / post create
signs: FeatureClass = gpkg.create_feature_class(
    'signs_p', srs=SRS, fields=fields)
# no spatial index
assert signs.has_spatial_index is False
signs.add_spatial_index()
# spatial index now present
assert signs.has_spatial_index is True
```

Refer to **SQLite** [documentation](https://www.sqlite.org/rtree.html#using_r_trees_effectively) 
on how to use these indexes for faster filtering / querying.  Also note
how to handle [round off error](https://www.sqlite.org/rtree.html#roundoff_error) 
when querying.


### Metadata Extension
Metadata Extension implementation based on [F.8. Metadata](http://www.geopackage.org/spec131/index.html#extension_metadata)
of the **GeoPackage Encoding Standard**.

The metadata extension is enabled at the GeoPackage level applying to all
tables and feature classes.  That said, not every table and feature class is 
required to have metadata.  

Metadata extension can be enabled at create time for a GeoPackage or 
can be enabled on an existing GeoPackage.

```python
from fudgeo.geopkg import GeoPackage

# enable metadata at create time
gpkg: GeoPackage = GeoPackage.create('../data/metadata.gpkg', enable_metadata=True)
assert gpkg.is_metadata_enabled is True

# enable metadata on an existing GeoPackage
gpkg: GeoPackage = GeoPackage('../data/example.gpkg')
assert gpkg.is_metadata_enabled is False
gpkg.enable_metadata_extension()
assert gpkg.is_metadata_enabled is True
```

```python
from fudgeo.enumeration import MetadataScope
from fudgeo.extension.metadata import TableReference
from fudgeo.geopkg import GeoPackage

# open GeoPackage with metadata extension enabled
gpkg: GeoPackage = GeoPackage('../data/example.gpkg')

# open a metadata xml file and add it to the GeoPackage
with open(...) as fin:
    id_ = gpkg.metadata.add_metadata(
        uri='https://www.isotc211.org/2005/gmd',
        scope=MetadataScope.dataset, metadata=fin.read()
    )
# apply the metadata to a feature class
reference = TableReference(table_name='road_l', file_id=id_)
gpkg.metadata.add_references(reference)
```

Support provided for the following reference types:
* `GeoPackageReference` -- used for `GeoPackage` level metadata
* `TableReference` -- used for `Table` and `FeatureClass` level metadata
* `ColumnReference` -- used for a **column** in a `Table` or `FeatureClass`
* `RowReference` -- used for a **row** in a `Table` or `FeatureClass`
* `RowColumnReference` -- used for **row / column** combination in a `Table` or `FeatureClass`


### Schema Extension
Schema Extension implementation based on [F.9. Schema](http://www.geopackage.org/spec131/index.html#extension_schema)
of the **GeoPackage Encoding Standard**.

The schema extension is enabled at the GeoPackage level and allows for extended
definitions on column names (e.g. name, title, description) and for constraints
to be defined for columns.  Constraints definitions are intended for 
applications usage and, while similar, are not the same as database constraints.

Schema extension can be enabled at create time for a GeoPackage or 
can be enabled on an existing GeoPackage.


```python
from fudgeo.geopkg import GeoPackage

# enable schema at create time
gpkg: GeoPackage = GeoPackage.create('../data/schema.gpkg', enable_schema=True)
assert gpkg.is_schema_enabled is True

# enable schema on an existing GeoPackage
gpkg: GeoPackage = GeoPackage('../data/example.gpkg')
assert gpkg.is_schema_enabled is False
gpkg.enable_schema_extension()
assert gpkg.is_schema_enabled is True
```

```python
from fudgeo.extension.schema import (
    EnumerationConstraint, GlobConstraint, RangeConstraint)
from fudgeo.geopkg import GeoPackage

# open GeoPackage with schema extension enabled
gpkg: GeoPackage = GeoPackage('../data/example.gpkg')

# add constraints for use with column definitions
constraints = [
    EnumerationConstraint(name='odds', values=[1, 3, 5, 7, 9]),
    EnumerationConstraint(name='colors', values=['red', 'yellow', 'blue']),
    GlobConstraint(name='pin', pattern='[0-9][0-9][0-9][0-9]'),
    RangeConstraint(name='exertion', min_value=6, max_value=20),
    RangeConstraint(name='longitude', min_value=-180, max_value=180),
    RangeConstraint(name='latitude', min_value=90, max_value=90),
]
gpkg.schema.add_constraints(constraints)

# use constrains and set some additional details for column name
gpkg.schema.add_column_definition(
    table_name='road_l', column_name='begin_longitude', 
    name='Beginning Longitude for Road', title='Begin Longitude', 
    constraint_name='longitude')
gpkg.schema.add_column_definition(
    table_name='road_l', column_name='begin_latitude', 
    name='Beginning Latitude for Road', title='Begin Latitude', 
    constraint_name='latitude')
gpkg.schema.add_column_definition(
    table_name='road_l', column_name='end_longitude', 
    name='Ending Longitude for Road', title='End Longitude', 
    constraint_name='longitude')
gpkg.schema.add_column_definition(
    table_name='road_l', column_name='end_latitude', 
    name='Ending Latitude for Road', title='End Latitude', 
    constraint_name='latitude')
```

Support provided for the following constraint types:
* `EnumerationConstraint` -- restrict to one or more values
* `GlobConstraint` -- pattern match based constraint
* `RangeConstraint` -- value constrained within a range, optionally including the bounds


## License

[MIT](https://choosealicense.com/licenses/mit/)

## Release History

### v0.7.0
* add support for **schema** extension
* add support for **metadata** extension
* add `__geo_interface__` to geometry classes
* introduce `bounding_box` property on `Envelope` class
* introduce `as_tuple` method on `Point` classes
* add `extension` sub-package, move `spatial` module into `extension`
* add `spatial_index_name` property on `FeatureClass`, returns the index table name
* enable enforcement of foreign key constraints
* reorganize code to handle OGR contents like an extension
* move protected functions from `geopkg` module into `util` module and rename
* add type hinting to `enumerations` module
* move `EnvelopeCode` into `enumerations`

### v0.6.0
* change `ogr_contents` default value to `False` (breaking change)
* add `spatial_index` option to `FeatureClass` creation, default to `False`
* add `add_spatial_index` method to `FeatureClass` for adding spatial index post creation
* add `has_spatial_index` property to `FeatureClass`
* add `count` property to `Table` and `FeatureClass`
* add `primary_key_field` property to `Table` and `FeatureClass`
* small speed-up to `Point` unpacking
* update `is_empty` to rely on internal attribute data type
* improvements to SQL statements to handle names that must be escaped
* bump `user_version` to reflect adopted version 1.3.1 of OGC GeoPackage
* add optional views for geometry columns and spatial references

### v0.5.2
* store empty state on the instance during geometry read
* introduce base classes for common capability and parametrize via class attributes
* add stub files to provide type hinting specialization 

### v0.5.1
* small performance improvements by reducing `bytes` concatenation and building up `bytearray`

### v0.5.0
* performance improvements for geometry reading (especially for geometries with large numbers of points / parts)
* performance improvements for geometry writing
* incorporated `numpy` and `bottleneck` as dependencies

### v0.4.2
* only unpack header and delay unpacking coordinates until needed
* write envelope to geometry header

### v0.4.1
* unpack envelope from header (when available)
* add `envelope` property to 2D, 3D, and 4D geometry
* derive envelope from underlying coordinates / geometries if not set from header

### v0.4.0
* add string representations to `GeoPackage`, `Table`, and `FeatureClass`
* allow optional creation of the `gpkg_ogr_contents`, defaults to True (create)
* split `geometry` module into a sub-package

### v0.3.10
* add `escaped_name` property to `BaseTable`, applies to `Table` and `FeatureClass`
* escape the name of input table / feature class during `create`

### v0.3.9
* quote reversal, doubles inside of singles for `escaped_name`

### v0.3.8
* add `fields` property to `BaseTable`, applies to `Table` and `FeatureClass`
* add `field_names` property to `BaseTable`, applies to `Table` and `FeatureClass`
* add `escaped_name` property to `Field` to return name valid for use in queries
* add type hinting to embedded sql statements and supporting values

### v0.3.7
* add `is_empty` property to geometries, improved handling for empty geometries
* update `user_version` to `10300`
* add handling for geometry headers with envelopes (skipping content)
* add type hinting to constants

### v0.3.6
* add `srs_id` (optional) to `SpatialReferenceSystem` instantiation, default to `org_coord_sys_id` if not specified

### v0.3.5
* store `coordinates` in attribute on 2D, 3D, and 4D geometry
* avoid creating points on instantiation of geometry
* expose `points` property to return point objects for 2D, 3D, and 4D geometry

### v0.3.4
* add `from_tuple` class methods to `Point`, `PointZ`, `PointM`, and `PointZM`

### v0.3.3
* catch possible exception when parsing microseconds from time
* add converter for `timestamp` to use same converter as `datetime`
* use lower case table names in queries

### v0.3.2
* include `PolygonM`, `PolygonZM`, `MultiPolygonM`, and `MultiPolygonZM` in geometry registration

### v0.3.1
* delay opening a `GeoPackage` connection until `connection` property is accessed 

### v0.3.0
* add support for `PolygonM`, `PolygonZM`, `MultiPolygonM`, and `MultiPolygonZM`
* add `geometry_column_name` and `geometry_type` properties to `FeatureClass`
* simplify query used by `has_z` and `has_m`

### v0.2.1
* improve `_convert_datetime` to handle different formats for timestamp (contributed by [@alexeygribko](https://github.com/alexeygribko))

### v0.2.0
* improve `_convert_datetime` to handle timezone
* add `DATETIME` tp `SQLFieldType`

### v0.1.2
* add option to overwrite feature classes and tables in `create_feature_class` and `create_table` methods 
* add option to overwrite in `create` method on `FeatureClass` and `Table` classes
* add `drop` method on `FeatureClass` and `Table` classes

### v0.1.1
* make compatible with Python 3.7 and up (update type hints, remove walrus)
* add support for OGR contents table (`gpkg_ogr_contents`) and triggers
* add `tables` and `feature_classes` properties to `GeoPackage` class
* include `application_id` and `user_version` in SQL definition
* fix timestamp format (was missing seconds)

### v0.1.0
* initial release, basic port of legacy `pygeopkg` package

            

Raw data

            {
    "_id": null,
    "home_page": "",
    "name": "fudgeo",
    "maintainer": "",
    "docs_url": null,
    "requires_python": ">=3.7",
    "maintainer_email": "",
    "keywords": "geopackage",
    "author": "",
    "author_email": "\"Integrated Informatics Inc.\" <contact@integrated-informatics.com>",
    "download_url": "",
    "platform": null,
    "description": "# fudgeo\n\n`fudgeo` removes the *fear uncertainty doubt* from using GeoPackages with \n`Python`. `fudgeo` is a lightweight package for creating OGC GeoPackages, Feature \nClasses, and Tables.  Easily read and write geometries and attributes to\nFeature Classes and Tables using regular `Python` objects and `SQLite` syntax.\n\nFor details on OGC GeoPackages, please see the [OGC web page](http://www.geopackage.org/).\n\n\n## Installation\n\n`fudgeo` is available from the [Python Package Index](https://pypi.org/project/fudgeo/).\n\n\n## Python Compatibility\n\nThe `fudgeo` library is compatible with Python 3.7 to 3.11.  Developed and \ntested on **macOS** and **Windows**, should be fine on **Linux** too.\n\n\n## Usage\n\n`fudgeo` can be used to: \n* Create a new empty `GeoPackage` or open an existing `GeoPackage`\n* Create new `FeatureClass` or `Table` with optional overwrite\n* Create `SpatialReferenceSystem` for a `FeatureClass`\n* Build geometry objects from lists of coordinate values\n* Work with data in `Table` or `FeatureClass` in a normal `SQLite` manner (e.g. `SELECT`, `INSERT`, `UPDATE`, `DELETE`)\n* Retrieve fields from a `FeatureClass` or `Table`\n* Access primary key field of `FeatureClass` or `Table` \n* Access geometry column name and geometry type for `FeatureClass`\n* Add spatial index on `FeatureClass`\n* Drop `FeatureClass` or `Table`\n\n\n### Create an Empty GeoPackage / Open GeoPackage\n\n```python\nfrom fudgeo.geopkg import GeoPackage\n\n# Creates an empty geopackage\ngpkg: GeoPackage = GeoPackage.create('../data/example.gpkg')\n\n# Opens an existing Geopackage (no validation)\ngpkg: GeoPackage = GeoPackage('../data/example.gpkg')\n```\n\n`GeoPackage`s are created with *three* default Spatial References defined\nautomatically, a pair of Spatial References to handle **undefined** cases,\nand a **WGS 84** entry. \n\nThe definition of the WGS84 entry is flexible - meaning that the \n*WKT for WGS84* can be setup per the users liking. As an example, \nuse with Esri's ArcGIS means either using the *EPSG WKT* or the *ESRI WKT*. By\ndefault the *ESRI WKT* is used - However, if *EPSG WKT* is desired, you\nmay provide a ``flavor`` parameter to the create method specifying EPSG.\n\n```python\nfrom fudgeo.geopkg import GeoPackage\n\n# Creates an empty geopackage using EPSG definitions\ngpkg: GeoPackage = GeoPackage.create('../temp/test.gpkg', flavor='EPSG')\n```\n\n### Create a Feature Class\n\nUse the `create_feature_class` method of a GeoPackage to make\na new feature class.  Feature classes require a name and a Spatial \nReference, the name must follow SQLite naming requirements.  Each\nfeature class is defined with `fid` and `SHAPE` fields, additional\nfields can be defined during creation.\n\nA Feature Class can be created with *Z* or *M* (or both) enabled. If \neither of these options are enabled, the geometry inserted into the \nFeature Class **must** include a value for the option specified.\n\n```python\nfrom typing import Tuple\nfrom fudgeo.enumeration import GeometryType, SQLFieldType\nfrom fudgeo.geopkg import FeatureClass, Field, GeoPackage, SpatialReferenceSystem\n\nSRS_WKT: str = (\n    'PROJCS[\"WGS_1984_UTM_Zone_23N\",'\n    'GEOGCS[\"GCS_WGS_1984\",'\n    'DATUM[\"D_WGS_1984\",'\n    'SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],'\n    'PRIMEM[\"Greenwich\",0.0],'\n    'UNIT[\"Degree\",0.0174532925199433]],'\n    'PROJECTION[\"Transverse_Mercator\"],'\n    'PARAMETER[\"False_Easting\",500000.0],'\n    'PARAMETER[\"False_Northing\",0.0],'\n    'PARAMETER[\"Central_Meridian\",-45.0],'\n    'PARAMETER[\"Scale_Factor\",0.9996],'\n    'PARAMETER[\"Latitude_Of_Origin\",0.0],'\n    'UNIT[\"Meter\",1.0]]')\n\nSRS: SpatialReferenceSystem = SpatialReferenceSystem(\n    name='WGS_1984_UTM_Zone_23N', organization='EPSG',\n    org_coord_sys_id=32623, definition=SRS_WKT)\nfields: Tuple[Field, ...] = (\n    Field('road_id', SQLFieldType.integer),\n    Field('name', SQLFieldType.text, size=100),\n    Field('begin_easting', SQLFieldType.double),\n    Field('begin_northing', SQLFieldType.double),\n    Field('end_easting', SQLFieldType.double),\n    Field('end_northing', SQLFieldType.double),\n    Field('begin_longitude', SQLFieldType.double),\n    Field('begin_latitude', SQLFieldType.double),\n    Field('end_longitude', SQLFieldType.double),\n    Field('end_latitude', SQLFieldType.double),\n    Field('is_one_way', SQLFieldType.boolean))\n\ngpkg: GeoPackage = GeoPackage.create('../temp/test.gpkg')\nfc: FeatureClass = gpkg.create_feature_class(\n    'road_l', srs=SRS, fields=fields, shape_type=GeometryType.linestring,\n    m_enabled=True, overwrite=True, spatial_index=True)\n```\n\n### About Spatial References For GeoPackages\n\nSpatial References in GeoPackages can use any definition from any \nauthority - be that `EPSG`, `ESRI`, or another authority. `fudgeo` imposes no \nrestriction and performs no checks on the definitions provided. Take care \nto ensure that the definitions are compatible with the platform / software \nyou intend to utilize with the `GeoPackage`.\n\n### Insert Features into a Feature Class (SQL)\n\nFeatures can be inserted into a Feature Class using SQL.\n\nThis example shows the creation of a random point Feature Class and\nbuilds upon the code from previous examples. Note that the create Feature Class\nportion of the code is omitted...\n\n```python\nfrom random import choice, randint\nfrom string import ascii_uppercase, digits\nfrom typing import List, Tuple\n\nfrom fudgeo.geometry import LineStringM\nfrom fudgeo.geopkg import GeoPackage\n\n# Generate some random points and attributes\nrows: List[Tuple[LineStringM, int, str, float, float, float, float, bool]] = []\nfor i in range(10000):\n    name = ''.join(choice(ascii_uppercase + digits) for _ in range(10))\n    road_id = randint(0, 1000)\n    eastings = [randint(300000, 600000) for _ in range(20)]\n    northings = [randint(1, 100000) for _ in range(20)]\n    coords = [(x, y, m) for m, (x, y) in enumerate(zip(eastings, northings))]\n    road = LineStringM(coords, srs_id=32623)\n    rows.append((road, road_id, name, eastings[0], northings[0],\n                 eastings[-1], northings[-1], False))\n\n# NOTE Builds from previous examples\ngpkg: GeoPackage = GeoPackage('../data/example.gpkg')   \nwith gpkg.connection as conn:\n    conn.executemany(\"\"\"\n        INSERT INTO road_l (SHAPE, road_id, name, begin_easting, begin_northing, \n                            end_easting, end_northing, is_one_way) \n           VALUES (?, ?, ?, ?, ?, ?, ?, ?)\"\"\", rows)\n```\n\n### Geometry Examples\n\nReview the tests for `fudgeo` for a comprehensive look into \ncreating geometries, below are some examples showing the simplicity\nof this package.\n\n\n```python\nfrom typing import List, Tuple\nfrom fudgeo.geometry import LineStringZM, Point, Polygon\n\n# Point in WGS 84\npt: Point = Point(x=-119, y=34)\n\n# Line with ZM Values for use with UTM Zone 23N (WGS 84)\ncoords: List[Tuple[float, float, float, float]] = [\n    (300000, 1, 10, 0), (300000, 4000000, 20, 1000),\n    (700000, 4000000, 30, 2000), (700000, 1, 40, 3000)]\nline: LineStringZM = LineStringZM(coords, srs_id=32623)\n\n# list of rings where a ring is simply the list of points it contains.\nrings: List[List[Tuple[float, float]]] = [\n    [(300000, 1), (300000, 4000000), (700000, 4000000), (700000, 1), (300000, 1)]]\npoly: Polygon = Polygon(rings, srs_id=32623)\n```\n\n### Select Features from GeoPackage (SQL)\n\nWhen selecting features from a GeoPackage feature class use SQL.  For \nthe most part (mainly simple geometries e.g. those without *Z* or *M*) this \ncan be done via a basic `SELECT` statement like:\n\n```python\ngpkg = GeoPackage(...)\ncursor = gpkg.connection.execute(\"\"\"SELECT SHAPE, example_id FROM point_fc\"\"\")\nfeatures = cursor.fetchall()\n```\n\nThis will return a list of tuples where each tuple contains a `Point`\nobject and an integer for `example_id` field.\n\nWhen working with extended geometry types (those with *Z* and/or *M*) \nthen the approach is to ensure `SQLite` knows how to convert the \ngeopackage stored geometry to a `fudgeo` geometry, this is done like so:\n\n```python\nfrom typing import List, Tuple\nfrom fudgeo.geometry import LineStringM\nfrom fudgeo.geopkg import GeoPackage\n\ngpkg: GeoPackage = GeoPackage('../data/example.gpkg')\ncursor = gpkg.connection.execute(\n    \"\"\"SELECT SHAPE \"[LineStringM]\", road_id FROM test\"\"\")\nfeatures: List[Tuple[LineStringM, int]] = cursor.fetchall()\n```\n\nor a little more general, accounting for extended geometry types and\npossibility of the geometry column being something other tha `SHAPE`:\n\n```python\nfrom typing import List, Tuple\nfrom fudgeo.geometry import LineStringM\nfrom fudgeo.geopkg import FeatureClass, GeoPackage\n\ngpkg: GeoPackage = GeoPackage('../data/example.gpkg')\nfc: FeatureClass = FeatureClass(geopackage=gpkg, name='road_l')\ncursor = gpkg.connection.execute(f\"\"\"\n    SELECT {fc.geometry_column_name} \"[{fc.geometry_type}]\", road_id \n    FROM {fc.escaped_name}\"\"\")\nfeatures: List[Tuple[LineStringM, int]] = cursor.fetchall()\n```\n\n\n## Extensions\n### Spatial Index Extension\nSpatial Index Extension implementation based on section [F.3. RTree Spatial Indexes](http://www.geopackage.org/spec131/index.html#extension_rtree)\nof the **GeoPackage Encoding Standard**.\n\nSpatial Indexes apply to individual feature classes.  A spatial index can be\nadded at create time or added on an existing feature class.\n\n```python\nfrom typing import Tuple\nfrom fudgeo.enumeration import SQLFieldType\nfrom fudgeo.geopkg import FeatureClass, Field, GeoPackage, SpatialReferenceSystem\n\n\nSRS_WKT: str = (\n    'PROJCS[\"WGS_1984_UTM_Zone_23N\",'\n    'GEOGCS[\"GCS_WGS_1984\",'\n    'DATUM[\"D_WGS_1984\",'\n    'SPHEROID[\"WGS_1984\",6378137.0,298.257223563]],'\n    'PRIMEM[\"Greenwich\",0.0],'\n    'UNIT[\"Degree\",0.0174532925199433]],'\n    'PROJECTION[\"Transverse_Mercator\"],'\n    'PARAMETER[\"False_Easting\",500000.0],'\n    'PARAMETER[\"False_Northing\",0.0],'\n    'PARAMETER[\"Central_Meridian\",-45.0],'\n    'PARAMETER[\"Scale_Factor\",0.9996],'\n    'PARAMETER[\"Latitude_Of_Origin\",0.0],'\n    'UNIT[\"Meter\",1.0]]')\nSRS: SpatialReferenceSystem = SpatialReferenceSystem(\n    name='WGS_1984_UTM_Zone_23N', organization='EPSG',\n    org_coord_sys_id=32623, definition=SRS_WKT)\nfields: Tuple[Field, ...] = (\n    Field('id', SQLFieldType.integer),\n    Field('name', SQLFieldType.text, size=100))\n\ngpkg: GeoPackage = GeoPackage.create('../temp/spatial_index.gpkg')\n# add spatial index at create time\nevent: FeatureClass = gpkg.create_feature_class(\n    'event_p', srs=SRS, fields=fields, spatial_index=True)\nassert event.has_spatial_index is True\n\n# add spatial index on an existing feature class / post create\nsigns: FeatureClass = gpkg.create_feature_class(\n    'signs_p', srs=SRS, fields=fields)\n# no spatial index\nassert signs.has_spatial_index is False\nsigns.add_spatial_index()\n# spatial index now present\nassert signs.has_spatial_index is True\n```\n\nRefer to **SQLite** [documentation](https://www.sqlite.org/rtree.html#using_r_trees_effectively) \non how to use these indexes for faster filtering / querying.  Also note\nhow to handle [round off error](https://www.sqlite.org/rtree.html#roundoff_error) \nwhen querying.\n\n\n### Metadata Extension\nMetadata Extension implementation based on [F.8. Metadata](http://www.geopackage.org/spec131/index.html#extension_metadata)\nof the **GeoPackage Encoding Standard**.\n\nThe metadata extension is enabled at the GeoPackage level applying to all\ntables and feature classes.  That said, not every table and feature class is \nrequired to have metadata.  \n\nMetadata extension can be enabled at create time for a GeoPackage or \ncan be enabled on an existing GeoPackage.\n\n```python\nfrom fudgeo.geopkg import GeoPackage\n\n# enable metadata at create time\ngpkg: GeoPackage = GeoPackage.create('../data/metadata.gpkg', enable_metadata=True)\nassert gpkg.is_metadata_enabled is True\n\n# enable metadata on an existing GeoPackage\ngpkg: GeoPackage = GeoPackage('../data/example.gpkg')\nassert gpkg.is_metadata_enabled is False\ngpkg.enable_metadata_extension()\nassert gpkg.is_metadata_enabled is True\n```\n\n```python\nfrom fudgeo.enumeration import MetadataScope\nfrom fudgeo.extension.metadata import TableReference\nfrom fudgeo.geopkg import GeoPackage\n\n# open GeoPackage with metadata extension enabled\ngpkg: GeoPackage = GeoPackage('../data/example.gpkg')\n\n# open a metadata xml file and add it to the GeoPackage\nwith open(...) as fin:\n    id_ = gpkg.metadata.add_metadata(\n        uri='https://www.isotc211.org/2005/gmd',\n        scope=MetadataScope.dataset, metadata=fin.read()\n    )\n# apply the metadata to a feature class\nreference = TableReference(table_name='road_l', file_id=id_)\ngpkg.metadata.add_references(reference)\n```\n\nSupport provided for the following reference types:\n* `GeoPackageReference` -- used for `GeoPackage` level metadata\n* `TableReference` -- used for `Table` and `FeatureClass` level metadata\n* `ColumnReference` -- used for a **column** in a `Table` or `FeatureClass`\n* `RowReference` -- used for a **row** in a `Table` or `FeatureClass`\n* `RowColumnReference` -- used for **row / column** combination in a `Table` or `FeatureClass`\n\n\n### Schema Extension\nSchema Extension implementation based on [F.9. Schema](http://www.geopackage.org/spec131/index.html#extension_schema)\nof the **GeoPackage Encoding Standard**.\n\nThe schema extension is enabled at the GeoPackage level and allows for extended\ndefinitions on column names (e.g. name, title, description) and for constraints\nto be defined for columns.  Constraints definitions are intended for \napplications usage and, while similar, are not the same as database constraints.\n\nSchema extension can be enabled at create time for a GeoPackage or \ncan be enabled on an existing GeoPackage.\n\n\n```python\nfrom fudgeo.geopkg import GeoPackage\n\n# enable schema at create time\ngpkg: GeoPackage = GeoPackage.create('../data/schema.gpkg', enable_schema=True)\nassert gpkg.is_schema_enabled is True\n\n# enable schema on an existing GeoPackage\ngpkg: GeoPackage = GeoPackage('../data/example.gpkg')\nassert gpkg.is_schema_enabled is False\ngpkg.enable_schema_extension()\nassert gpkg.is_schema_enabled is True\n```\n\n```python\nfrom fudgeo.extension.schema import (\n    EnumerationConstraint, GlobConstraint, RangeConstraint)\nfrom fudgeo.geopkg import GeoPackage\n\n# open GeoPackage with schema extension enabled\ngpkg: GeoPackage = GeoPackage('../data/example.gpkg')\n\n# add constraints for use with column definitions\nconstraints = [\n    EnumerationConstraint(name='odds', values=[1, 3, 5, 7, 9]),\n    EnumerationConstraint(name='colors', values=['red', 'yellow', 'blue']),\n    GlobConstraint(name='pin', pattern='[0-9][0-9][0-9][0-9]'),\n    RangeConstraint(name='exertion', min_value=6, max_value=20),\n    RangeConstraint(name='longitude', min_value=-180, max_value=180),\n    RangeConstraint(name='latitude', min_value=90, max_value=90),\n]\ngpkg.schema.add_constraints(constraints)\n\n# use constrains and set some additional details for column name\ngpkg.schema.add_column_definition(\n    table_name='road_l', column_name='begin_longitude', \n    name='Beginning Longitude for Road', title='Begin Longitude', \n    constraint_name='longitude')\ngpkg.schema.add_column_definition(\n    table_name='road_l', column_name='begin_latitude', \n    name='Beginning Latitude for Road', title='Begin Latitude', \n    constraint_name='latitude')\ngpkg.schema.add_column_definition(\n    table_name='road_l', column_name='end_longitude', \n    name='Ending Longitude for Road', title='End Longitude', \n    constraint_name='longitude')\ngpkg.schema.add_column_definition(\n    table_name='road_l', column_name='end_latitude', \n    name='Ending Latitude for Road', title='End Latitude', \n    constraint_name='latitude')\n```\n\nSupport provided for the following constraint types:\n* `EnumerationConstraint` -- restrict to one or more values\n* `GlobConstraint` -- pattern match based constraint\n* `RangeConstraint` -- value constrained within a range, optionally including the bounds\n\n\n## License\n\n[MIT](https://choosealicense.com/licenses/mit/)\n\n## Release History\n\n### v0.7.0\n* add support for **schema** extension\n* add support for **metadata** extension\n* add `__geo_interface__` to geometry classes\n* introduce `bounding_box` property on `Envelope` class\n* introduce `as_tuple` method on `Point` classes\n* add `extension` sub-package, move `spatial` module into `extension`\n* add `spatial_index_name` property on `FeatureClass`, returns the index table name\n* enable enforcement of foreign key constraints\n* reorganize code to handle OGR contents like an extension\n* move protected functions from `geopkg` module into `util` module and rename\n* add type hinting to `enumerations` module\n* move `EnvelopeCode` into `enumerations`\n\n### v0.6.0\n* change `ogr_contents` default value to `False` (breaking change)\n* add `spatial_index` option to `FeatureClass` creation, default to `False`\n* add `add_spatial_index` method to `FeatureClass` for adding spatial index post creation\n* add `has_spatial_index` property to `FeatureClass`\n* add `count` property to `Table` and `FeatureClass`\n* add `primary_key_field` property to `Table` and `FeatureClass`\n* small speed-up to `Point` unpacking\n* update `is_empty` to rely on internal attribute data type\n* improvements to SQL statements to handle names that must be escaped\n* bump `user_version` to reflect adopted version 1.3.1 of OGC GeoPackage\n* add optional views for geometry columns and spatial references\n\n### v0.5.2\n* store empty state on the instance during geometry read\n* introduce base classes for common capability and parametrize via class attributes\n* add stub files to provide type hinting specialization \n\n### v0.5.1\n* small performance improvements by reducing `bytes` concatenation and building up `bytearray`\n\n### v0.5.0\n* performance improvements for geometry reading (especially for geometries with large numbers of points / parts)\n* performance improvements for geometry writing\n* incorporated `numpy` and `bottleneck` as dependencies\n\n### v0.4.2\n* only unpack header and delay unpacking coordinates until needed\n* write envelope to geometry header\n\n### v0.4.1\n* unpack envelope from header (when available)\n* add `envelope` property to 2D, 3D, and 4D geometry\n* derive envelope from underlying coordinates / geometries if not set from header\n\n### v0.4.0\n* add string representations to `GeoPackage`, `Table`, and `FeatureClass`\n* allow optional creation of the `gpkg_ogr_contents`, defaults to True (create)\n* split `geometry` module into a sub-package\n\n### v0.3.10\n* add `escaped_name` property to `BaseTable`, applies to `Table` and `FeatureClass`\n* escape the name of input table / feature class during `create`\n\n### v0.3.9\n* quote reversal, doubles inside of singles for `escaped_name`\n\n### v0.3.8\n* add `fields` property to `BaseTable`, applies to `Table` and `FeatureClass`\n* add `field_names` property to `BaseTable`, applies to `Table` and `FeatureClass`\n* add `escaped_name` property to `Field` to return name valid for use in queries\n* add type hinting to embedded sql statements and supporting values\n\n### v0.3.7\n* add `is_empty` property to geometries, improved handling for empty geometries\n* update `user_version` to `10300`\n* add handling for geometry headers with envelopes (skipping content)\n* add type hinting to constants\n\n### v0.3.6\n* add `srs_id` (optional) to `SpatialReferenceSystem` instantiation, default to `org_coord_sys_id` if not specified\n\n### v0.3.5\n* store `coordinates` in attribute on 2D, 3D, and 4D geometry\n* avoid creating points on instantiation of geometry\n* expose `points` property to return point objects for 2D, 3D, and 4D geometry\n\n### v0.3.4\n* add `from_tuple` class methods to `Point`, `PointZ`, `PointM`, and `PointZM`\n\n### v0.3.3\n* catch possible exception when parsing microseconds from time\n* add converter for `timestamp` to use same converter as `datetime`\n* use lower case table names in queries\n\n### v0.3.2\n* include `PolygonM`, `PolygonZM`, `MultiPolygonM`, and `MultiPolygonZM` in geometry registration\n\n### v0.3.1\n* delay opening a `GeoPackage` connection until `connection` property is accessed \n\n### v0.3.0\n* add support for `PolygonM`, `PolygonZM`, `MultiPolygonM`, and `MultiPolygonZM`\n* add `geometry_column_name` and `geometry_type` properties to `FeatureClass`\n* simplify query used by `has_z` and `has_m`\n\n### v0.2.1\n* improve `_convert_datetime` to handle different formats for timestamp (contributed by [@alexeygribko](https://github.com/alexeygribko))\n\n### v0.2.0\n* improve `_convert_datetime` to handle timezone\n* add `DATETIME` tp `SQLFieldType`\n\n### v0.1.2\n* add option to overwrite feature classes and tables in `create_feature_class` and `create_table` methods \n* add option to overwrite in `create` method on `FeatureClass` and `Table` classes\n* add `drop` method on `FeatureClass` and `Table` classes\n\n### v0.1.1\n* make compatible with Python 3.7 and up (update type hints, remove walrus)\n* add support for OGR contents table (`gpkg_ogr_contents`) and triggers\n* add `tables` and `feature_classes` properties to `GeoPackage` class\n* include `application_id` and `user_version` in SQL definition\n* fix timestamp format (was missing seconds)\n\n### v0.1.0\n* initial release, basic port of legacy `pygeopkg` package\n",
    "bugtrack_url": null,
    "license": "MIT License  Copyright (c) 2021-2023 Integrated Informatics Inc  Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:  The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.  THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ",
    "summary": "GeoPackage support from Python.  fudgeo is a lightweight package for creating OGC GeoPackages, Feature Classes, and Tables.  Easily read and write geometries and attributes to Feature Classes and Tables using regular Python objects and SQLite syntax.",
    "version": "0.7.0",
    "project_urls": {
        "Homepage": "https://github.com/realiii/fudgeo"
    },
    "split_keywords": [
        "geopackage"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "89f54b3635c4401ea2b5c6211cd96f90b2a2fdeef82731f16917663eefb20965",
                "md5": "5a9a39c2b8affd272bf7c732b2d1195e",
                "sha256": "f0d2c3db56047757743b46468cd3083bd3613f462ea631a960a4f168787793d6"
            },
            "downloads": -1,
            "filename": "fudgeo-0.7.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "5a9a39c2b8affd272bf7c732b2d1195e",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.7",
            "size": 45555,
            "upload_time": "2023-06-10T14:44:49",
            "upload_time_iso_8601": "2023-06-10T14:44:49.381692Z",
            "url": "https://files.pythonhosted.org/packages/89/f5/4b3635c4401ea2b5c6211cd96f90b2a2fdeef82731f16917663eefb20965/fudgeo-0.7.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-06-10 14:44:49",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "realiii",
    "github_project": "fudgeo",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "lcname": "fudgeo"
}
        
Elapsed time: 0.07752s