# Geostructures
[](https://github.com/ccbest/geostructures/actions/workflows/unit-tests.yml)
A lightweight implementation of shapes drawn across a geo-temporal plane.
Geostructures makes it easy to:
* Define a variety of common shapes used for geospatial analysis
* Perform common geospatial and geometric calculations
* Convert to/from a variety of geospatial formats
* Temporally bound data by instant or interval
* Convert shapes and coordinates to/from geohashes
<img src="./static/display_img.PNG" alt="plotting" width="800" height="300"/>
### Installation
Geostructures is available on PYPI
```
$ pip install geostructures
```
#### Requirements
Geostructures only requires `numpy` to function.
#### Optional Requirements
* `pip install geostructures[df]`
* Add dataframe support for geopandas and pandas
* `pip install geostructures[kml]`
* Add KML read/write support using FastKML
* `pip install geostructures[h3]`
* Add support for geohashing using Uber's H3 algorithm
* `pip install geostructures[mgrs]`
* Add support for converting coordinates using the Military Grid Reference System (MGRS)
* `pip install geostructures[proj]`
* Add support for coordinate projection conversion
### Overview
Geostructures provides a python interface for functionally defining various shapes drawn on a map. Unlike other
libraries such as Shapely, these shapes retain their mathematical definitions rather than being simplified into N-sided polygons.
The shapes currently supported are:
* Boxes
* Circles
* Ellipses
* LineStrings
* Points
* Polygons
* Rings/Wedges
All shapes may optionally be temporally-bound using a specific datetime or a datetime interval.
Additionally, geostructures provides convenience objects for representing chronologically-ordered (`Track`) and unordered (`FeatureCollection`) collections of the above shapes.
## Quick Start
For an interactive introduction, please review our collection of [Jupyter notebooks](./notebooks).
#### Working with Coordinates
Geostructures uses WGS84 (EPSG4326) for all operations, but supports conversion to and from
a variety of coordinate formats. Z and M values are supported and will be preserved when converting
to formats that support them (e.g. ESRI shapefile).
**Note**: Geostructures represents coordinates in (longitude, latitude) order.
```python
from geostructures import *
coord = Coordinate(
longitude=-0.154092,
latitude=51.539865
)
coord.to_float() # (-0.154092, 51.539865)
coord.to_str() # ('-0.154092', '51.539865')
# Degrees, Minutes, Seconds
coord.to_dms()
coord.from_dms((0, 9, 14.7312, 'W'), (51, 32, 23.514, 'N'))
# Quadrant, Degrees, Minutes, Seconds
coord.to_qdms()
coord.from_qdms('W000091473', 'N51322351')
# Non-WGS84 Projection
coord.to_projection('EPSG:27700')
coord.from_projection(-16.01032599998871, -6.869540999992751, 'EPSG:27700')
# Military Grid Reference System (MGRS) (requires geostructures[mgrs])
coord.to_mgrs()
coord.from_mgrs('30UXC9735113702')
# Add Z and M values
coord = Coordinate(
longitude=-0.154092,
latitude=51.539865,
z=100,
m=200
)
```
#### Creating GeoShapes
```python
from geostructures import *
box = GeoBox(
Coordinate(-0.154092, 51.539865), # Northwest corner
Coordinate(-0.140592, 51.505665), # Southeast corner
)
circle = GeoCircle(
Coordinate(-0.131092, 51.509865), # centerpoint
radius=500,
)
ellipse = GeoEllipse(
Coordinate(-0.093092, 51.529865), # centerpoint
semi_major=1_000, # The distance between the centerpoint and the furthest point along the circumference
semi_minor=250, # The distance between the centerpoint and the closest point along the circumference
rotation=45, # The angle of rotation (between 0 and 360)
)
ring = GeoRing(
Coordinate(-0.116092, 51.519865), # centerpoint
inner_radius=800,
outer_radius=1000,
properties={"name": "ring"}
)
# Same as a ring, but with a min/max angle
wedge = GeoRing(
Coordinate(-0.101092, 51.514865), # centerpoint
inner_radius=300,
outer_radius=500,
angle_min=60, # The minimum angle of the wedge
angle_max=190, # The maximum angle of the wedge
)
linestring = GeoLineString(
[
Coordinate(-0.123092, 51.515865), Coordinate(-0.118092, 51.514665), Coordinate(-0.116092, 51.514865),
Coordinate(-0.116092, 51.518865), Coordinate(-0.108092, 51.512865)
],
)
point = GeoPoint(
Coordinate(-0.116092, 51.519865),
)
polygon = GeoPolygon(
[
Coordinate(-0.116092, 51.509865), Coordinate(-0.111092, 51.509865),
Coordinate(-0.113092, 51.506865), Coordinate(-0.116092, 51.509865) # Note that the last coordinate is the same as the first
],
)
```
#### Cutting Holes
Holes are defined using GeoShapes and can be cut from any individual shape (on its own or as a component of a multishape)
```python
from geostructures import *
circle = GeoCircle(
Coordinate(-0.131092, 51.509865),
radius=500,
holes=[
GeoCircle(Coordinate(-0.131092, 51.509865), 250)
]
)
```
#### Defining Properties
You can attach whatever properties you want to any shape. Where supported (e.g. GeoJSON and shapefiles), these
properties will remain with the shape when you convert it to a different format.
```python
from geostructures import *
# You can define properties upon instantiation
point = GeoPoint(
Coordinate(-0.116092, 51.519865),
properties={
'example': 'property'
}
)
# Or at any time afterwards (will mutate the shape)
point.set_property(
'example', # The property key
2 # The property value
)
```
#### Creating MultiShapes
Multishapes are treated as lists of GeoShapes (of their corresponding type) and can be assigned
properties/holes/time bounds in the same way.
```python
from geostructures import *
# Multipolygons
multipolygon = MultiGeoPolygon(
[
GeoCircle(Coordinate(-0.131092, 51.509865), 500),
GeoBox(Coordinate(-0.154092, 51.539865), Coordinate(-0.140592, 51.505665))
],
dt=...,
properties=...,
)
multipoint = MultiGeoPoint(
[
GeoPoint(Coordinate(-0.154092, 51.539865)),
GeoPoint(Coordinate(-0.140592, 51.505665))
],
dt=...,
properties=...,
)
multilinestring = MultiGeoLineString(
[
GeoLineString([Coordinate(-0.154092, 51.539865), Coordinate(-0.140592, 51.505665), ...]),
GeoLineString([Coordinate(-0.116092, 51.518865), Coordinate(-0.108092, 51.512865), ...]),
],
dt=...,
properties=...,
)
```
#### Geometric Operations
Geostructures isn't a replacement for shapely (see conversion to shapely below), however supports
many geometric operations.
```python
from datetime import datetime
from geostructures import *
circle = GeoCircle(Coordinate(-0.131092, 51.509865), radius=500, dt=datetime(2020, 1, 1))
ellipse = GeoEllipse(Coordinate(-0.093092, 51.529865), semi_major=1_000, semi_minor=250, rotation=45,
dt=datetime(2020, 1, 1))
# True/False, do these shapes intersect?
circle.intersects(ellipse) # Both temporally and spatially
circle.intersects_shape(ellipse) # Only spatially
# True/False, does the circle fully contain the ellipse?
circle.contains(ellipse) # Both temporally and spatially
circle.contains_shape(ellipse) # Only spatially
# Return the rectangle that circumscribes this shape (as a GeoBox)
circle.circumscribing_rectangle()
# Return the circle that circumscribes this shape (as a GeoCircle)
ellipse.circumscribing_circle()
# Get the xmin, xmax, ymin, ymax of this shape
circle.bounds
# Get the area of this shape in meters squared (requires pyproj)
circle.area
# Get the volume of this shape in meters squared seconds (requires pyproj)
circle.volume
# Get the coordinates that define this shapes outer shell
circle.bounding_coords() # default 36 bounding points
circle.bounding_coords(k=360) # or define the number of points to increase/decrease precision
# Get a list of all the linear rings that comprise this shape (includes holes)
circle.linear_rings() # Also accepts k
# Return the convex hull as a GeoPolygon (only for multi-shapes and collections)
multishape = MultiGeoPolygon([circle, ellipse])
multishape.convex_hull() # Also accepts k
```
#### Converting Between Formats
All objects can be converted to/from most common geospatial formats.
```python
from geostructures import *
from geostructures.collections import FeatureCollection
polygon = GeoPolygon(
[
Coordinate(-0.116092, 51.509865), Coordinate(-0.111092, 51.509865),
Coordinate(-0.113092, 51.506865), Coordinate(-0.116092, 51.509865)
]
)
# GeoJSON
polygon.to_geojson()
polygon.from_geojson( { a geojson object } )
# Well-Known Text (WKT)
polygon.to_wkt()
polygon.from_wkt( '<a wkt polygon string>' )
# Python Shapely
polygon.to_shapely()
polygon.from_shapely( a shapely polygon )
# FastKML
from fastkml import KML
k = KML() # also works with fastkml.Folder
k.append(polygon.to_fastkml_placemark())
polygon.from_fastkml_placemark(k.features[0])
# Collections (and Tracks) of shapes have additional supported formats
collection = FeatureCollection([polygon])
# Creates a geopandas DataFrame
collection.to_geopandas()
collection.from_geopandas( a geopandas DataFrame )
# Creates a GeoJSON FeatureCollection
collection.to_geojson()
collection.from_geojson( { a geojson featurecollection } )
# Read/Write a FeatureCollection to an ESRI Shapefile
from zipfile import ZipFile
with ZipFile('shapefile_name.zip', 'w') as zfile:
collection.to_shapefile(zfile)
collection.from_shapefile('shapefile_name.zip')
# Shapefiles may contain multiple feature layers, so you can control which ones get read
collection.from_shapefile('shapefile_name.zip', read_layers=['layer1', 'layer2'])
```
#### Bounding Shapes by Time
All shapes can be bound by time instants or intervals
```python
from datetime import datetime, timedelta
from geostructures import *
from geostructures.time import TimeInterval
# Shapes can be bounded by a datetime to represent an instant in time
point_in_time = GeoPoint(Coordinate(-0.154092, 51.539865), dt=datetime(2020, 5, 1, 12))
# Or they can be bounded by a time interval
span_of_time = GeoPoint(
Coordinate(-0.155092, 51.540865),
dt=TimeInterval(
datetime(2020, 5, 1, 13),
datetime(2020, 5, 1, 14)
)
)
# Time spans can also be set after instantiation
another_point = GeoPoint(Coordinate(-0.154092, 51.539865))
another_point.set_dt(TimeInterval(datetime(2020, 5, 1, 16), datetime(2020, 5, 1, 17)))
# You can buffer a time-bound shape with a timedelta
another_point.buffer_dt(timedelta(hours=6))
# Or strip shape's time bounds
another_point.strip_dt()
# Collections where all underlying shapes are time-bound can be represented as a Track, which
# supports additional features
track = Track([point_in_time, span_of_time])
# Slice by datetime
subset = track[datetime(2020, 5, 1, 12):datetime(2020, 5, 1, 13)]
# Get metrics between shapes
track.centroid_distances # meters
track.speed_diffs # meters per second
track.time_start_diffs # timedeltas
# Remove shapes that are spatially distant but chronologically close
track.filter_impossible_journeys(max_speed=5) # meters per second
```
#### Geohashing
Geostructures supports geohashing using both Uber's H3 and the original Niemeyer geohashing algorithm.
```python
from geostructures import *
from geostructures.geohash import H3Hasher, NiemeyerHasher, h3_to_geopolygon, niemeyer_to_geobox
box = GeoBox(Coordinate(-0.154092, 51.539865), Coordinate(-0.140592, 51.505665))
circle = GeoCircle(Coordinate(-0.131092, 51.509865), radius=500)
collection = FeatureCollection([box, circle])
# Create a H3 hasher
hasher = H3Hasher(resolution=10)
# Hash a singular shape to return the list of geohashes
set_of_geohashes = hasher.hash_shape(box)
# Convert the geohash into its corresponding GeoShape
geopolygon = h3_to_geopolygon(set_of_geohashes.pop())
# Hash a collection of shapes to return a dictionary of { geohash: [ corresponding geoshapes ] }
# or supply a custom aggregation function
hashmap = hasher.hash_collection(collection)
# Alternatively, hash using the Niemeyer algorithm
hasher = NiemeyerHasher(length=8, base=16)
set_of_geohashes = hasher.hash_shape(box)
geobox = niemeyer_to_geobox(set_of_geohashes.pop(), base=16)
hashmap = hasher.hash_collection(collection)
```
#### Common Geospatial Calculations
```python
from geostructures import Coordinate
from geostructures.calc import *
# The straight-line direction of travel to get from point A to point B
bearing_degrees(Coordinate(-0.154092, 51.539865), Coordinate(-0.140592, 51.505665))
# The great-sphere distance between two points
haversine_distance_meters(Coordinate(-0.154092, 51.539865), Coordinate(-0.140592, 51.505665))
# The resulting coordinate of traveling some distance from point A in a given direction
inverse_haversine_degrees(
Coordinate(-0.154092, 51.539865),
45, # degrees clockwise from true north
200 # the distance traveled (in meters)
)
# The same, except using radians for direction of travel
inverse_haversine_radians(Coordinate(-0.154092, 51.539865), 0.7853981633974483, 200)
# Rotate coordinates around a defined origin
rotate_coordinates(
[
Coordinate(-0.154092, 51.539865),
Coordinate(-0.140592, 51.505665)
],
origin=Coordinate(-0.16, 50.24),
degrees=45,
)
```
### Projections
This library assumes that all geospatial terms and structures conform to the
[WGS84 standard](https://en.wikipedia.org/wiki/World_Geodetic_System) (CRS 4326).
### Sourcing
This library is designed to implement and extend the requirements of geospatial data laid out by:
* [GML 3.1.1 PIDF-LO Shape Application Schema (PDF Download)](https://portal.ogc.org/files/?artifact_id=21630)
* [RFC5491 (GEOPRIV PIDF-LO Usage)](https://www.rfc-editor.org/rfc/rfc5491.txt)
* [RFC7946 (The GeoJSON Format)](https://datatracker.ietf.org/doc/html/rfc7946)
* [ESRI Shapefile Technical Description - July 1998](http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf)
### Extensions / Related Projects
**[Geochron](https://github.com/etalbert102/geochron)**
A companion package to geostructures enabling geo-spatial-temporal data structures
### Reporting Issues / Requesting Features
The Geostructures team uses Github issues to track development goals. Please be as descriptive
as possible so we can effectively triage your request.
### Contributing
We welcome all contributors! Please review [CONTRIBUTING.md](./CONTRIBUTING.md) for more information.
### Developers
Carl Best (Sr. Data Scientist/Project Owner)\
https://github.com/ccbest/
Eli Talbert (Sr. Data Scientist/PhD)\
https://github.com/etalbert102
Jessica Moore (Sr. Data Scientist)\
https://github.com/jessica-writes-code
Richard Marshall (Data Scientist/SME)\
https://github.com/RichardMarshall13
Raw data
{
"_id": null,
"home_page": "https://github.com/ccbest/geostructures",
"name": "geostructures",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.6",
"maintainer_email": null,
"keywords": null,
"author": "Carl Best",
"author_email": null,
"download_url": "https://files.pythonhosted.org/packages/62/52/22e793e151f065bd02d4254996ac266ef34e10c913c179db0c785979e93e/geostructures-0.12.2.tar.gz",
"platform": null,
"description": "\n# Geostructures\n\n[](https://github.com/ccbest/geostructures/actions/workflows/unit-tests.yml)\n\nA lightweight implementation of shapes drawn across a geo-temporal plane.\n\nGeostructures makes it easy to:\n* Define a variety of common shapes used for geospatial analysis\n* Perform common geospatial and geometric calculations\n* Convert to/from a variety of geospatial formats\n* Temporally bound data by instant or interval\n* Convert shapes and coordinates to/from geohashes\n\n<img src=\"./static/display_img.PNG\" alt=\"plotting\" width=\"800\" height=\"300\"/>\n\n### Installation\n\nGeostructures is available on PYPI\n```\n$ pip install geostructures\n```\n\n#### Requirements\n\nGeostructures only requires `numpy` to function.\n\n#### Optional Requirements\n\n* `pip install geostructures[df]`\n * Add dataframe support for geopandas and pandas\n\n* `pip install geostructures[kml]`\n * Add KML read/write support using FastKML\n\n* `pip install geostructures[h3]`\n * Add support for geohashing using Uber's H3 algorithm\n\n* `pip install geostructures[mgrs]`\n * Add support for converting coordinates using the Military Grid Reference System (MGRS) \n\n* `pip install geostructures[proj]`\n * Add support for coordinate projection conversion\n\n\n### Overview\n\nGeostructures provides a python interface for functionally defining various shapes drawn on a map. Unlike other \nlibraries such as Shapely, these shapes retain their mathematical definitions rather than being simplified into N-sided polygons.\n\nThe shapes currently supported are:\n* Boxes\n* Circles\n* Ellipses\n* LineStrings\n* Points\n* Polygons\n* Rings/Wedges\n\nAll shapes may optionally be temporally-bound using a specific datetime or a datetime interval.\n\nAdditionally, geostructures provides convenience objects for representing chronologically-ordered (`Track`) and unordered (`FeatureCollection`) collections of the above shapes.\n\n## Quick Start\n\nFor an interactive introduction, please review our collection of [Jupyter notebooks](./notebooks).\n\n#### Working with Coordinates\nGeostructures uses WGS84 (EPSG4326) for all operations, but supports conversion to and from \na variety of coordinate formats. Z and M values are supported and will be preserved when converting\nto formats that support them (e.g. ESRI shapefile).\n\n**Note**: Geostructures represents coordinates in (longitude, latitude) order.\n```python\nfrom geostructures import *\n\ncoord = Coordinate(\n longitude=-0.154092,\n latitude=51.539865\n)\n\ncoord.to_float() # (-0.154092, 51.539865)\ncoord.to_str() # ('-0.154092', '51.539865')\n\n# Degrees, Minutes, Seconds\ncoord.to_dms()\ncoord.from_dms((0, 9, 14.7312, 'W'), (51, 32, 23.514, 'N'))\n\n# Quadrant, Degrees, Minutes, Seconds\ncoord.to_qdms()\ncoord.from_qdms('W000091473', 'N51322351')\n\n# Non-WGS84 Projection\ncoord.to_projection('EPSG:27700')\ncoord.from_projection(-16.01032599998871, -6.869540999992751, 'EPSG:27700')\n\n# Military Grid Reference System (MGRS) (requires geostructures[mgrs])\ncoord.to_mgrs()\ncoord.from_mgrs('30UXC9735113702')\n\n# Add Z and M values\ncoord = Coordinate(\n longitude=-0.154092,\n latitude=51.539865,\n z=100,\n m=200\n)\n```\n\n#### Creating GeoShapes\n```python\nfrom geostructures import *\n\nbox = GeoBox(\n Coordinate(-0.154092, 51.539865), # Northwest corner\n Coordinate(-0.140592, 51.505665), # Southeast corner\n)\n\ncircle = GeoCircle(\n Coordinate(-0.131092, 51.509865), # centerpoint\n radius=500, \n)\n\nellipse = GeoEllipse(\n Coordinate(-0.093092, 51.529865), # centerpoint\n semi_major=1_000, # The distance between the centerpoint and the furthest point along the circumference\n semi_minor=250, # The distance between the centerpoint and the closest point along the circumference\n rotation=45, # The angle of rotation (between 0 and 360)\n)\n\nring = GeoRing(\n Coordinate(-0.116092, 51.519865), # centerpoint\n inner_radius=800,\n outer_radius=1000,\n properties={\"name\": \"ring\"}\n)\n\n# Same as a ring, but with a min/max angle\nwedge = GeoRing(\n Coordinate(-0.101092, 51.514865), # centerpoint\n inner_radius=300,\n outer_radius=500,\n angle_min=60, # The minimum angle of the wedge\n angle_max=190, # The maximum angle of the wedge\n)\n\nlinestring = GeoLineString(\n [\n Coordinate(-0.123092, 51.515865), Coordinate(-0.118092, 51.514665), Coordinate(-0.116092, 51.514865),\n Coordinate(-0.116092, 51.518865), Coordinate(-0.108092, 51.512865)\n ],\n)\n\npoint = GeoPoint(\n Coordinate(-0.116092, 51.519865), \n)\n\npolygon = GeoPolygon(\n [\n Coordinate(-0.116092, 51.509865), Coordinate(-0.111092, 51.509865), \n Coordinate(-0.113092, 51.506865), Coordinate(-0.116092, 51.509865) # Note that the last coordinate is the same as the first\n ],\n)\n```\n\n#### Cutting Holes\nHoles are defined using GeoShapes and can be cut from any individual shape (on its own or as a component of a multishape)\n```python\nfrom geostructures import *\n\ncircle = GeoCircle(\n Coordinate(-0.131092, 51.509865), \n radius=500, \n holes=[\n GeoCircle(Coordinate(-0.131092, 51.509865), 250)\n ]\n)\n\n```\n\n#### Defining Properties\nYou can attach whatever properties you want to any shape. Where supported (e.g. GeoJSON and shapefiles), these\nproperties will remain with the shape when you convert it to a different format.\n```python\nfrom geostructures import *\n\n# You can define properties upon instantiation\npoint = GeoPoint(\n Coordinate(-0.116092, 51.519865), \n properties={\n 'example': 'property'\n }\n)\n\n# Or at any time afterwards (will mutate the shape)\npoint.set_property(\n 'example', # The property key\n 2 # The property value\n)\n\n```\n\n#### Creating MultiShapes\nMultishapes are treated as lists of GeoShapes (of their corresponding type) and can be assigned \nproperties/holes/time bounds in the same way.\n\n```python\nfrom geostructures import *\n\n# Multipolygons \nmultipolygon = MultiGeoPolygon(\n [\n GeoCircle(Coordinate(-0.131092, 51.509865), 500),\n GeoBox(Coordinate(-0.154092, 51.539865), Coordinate(-0.140592, 51.505665))\n ],\n dt=...,\n properties=...,\n)\n\nmultipoint = MultiGeoPoint(\n [\n GeoPoint(Coordinate(-0.154092, 51.539865)),\n GeoPoint(Coordinate(-0.140592, 51.505665))\n ],\n dt=...,\n properties=...,\n)\n\nmultilinestring = MultiGeoLineString(\n [\n GeoLineString([Coordinate(-0.154092, 51.539865), Coordinate(-0.140592, 51.505665), ...]),\n GeoLineString([Coordinate(-0.116092, 51.518865), Coordinate(-0.108092, 51.512865), ...]),\n ],\n dt=...,\n properties=...,\n)\n```\n\n#### Geometric Operations\nGeostructures isn't a replacement for shapely (see conversion to shapely below), however supports \nmany geometric operations.\n\n```python\nfrom datetime import datetime\nfrom geostructures import *\n\ncircle = GeoCircle(Coordinate(-0.131092, 51.509865), radius=500, dt=datetime(2020, 1, 1))\nellipse = GeoEllipse(Coordinate(-0.093092, 51.529865), semi_major=1_000, semi_minor=250, rotation=45,\n dt=datetime(2020, 1, 1))\n\n# True/False, do these shapes intersect?\ncircle.intersects(ellipse) # Both temporally and spatially\ncircle.intersects_shape(ellipse) # Only spatially\n\n# True/False, does the circle fully contain the ellipse?\ncircle.contains(ellipse) # Both temporally and spatially\ncircle.contains_shape(ellipse) # Only spatially\n\n# Return the rectangle that circumscribes this shape (as a GeoBox)\ncircle.circumscribing_rectangle()\n\n# Return the circle that circumscribes this shape (as a GeoCircle)\nellipse.circumscribing_circle()\n\n# Get the xmin, xmax, ymin, ymax of this shape\ncircle.bounds\n\n# Get the area of this shape in meters squared (requires pyproj)\ncircle.area\n\n# Get the volume of this shape in meters squared seconds (requires pyproj)\ncircle.volume\n\n# Get the coordinates that define this shapes outer shell\ncircle.bounding_coords() # default 36 bounding points\ncircle.bounding_coords(k=360) # or define the number of points to increase/decrease precision\n\n# Get a list of all the linear rings that comprise this shape (includes holes)\ncircle.linear_rings() # Also accepts k\n\n# Return the convex hull as a GeoPolygon (only for multi-shapes and collections)\nmultishape = MultiGeoPolygon([circle, ellipse])\nmultishape.convex_hull() # Also accepts k\n```\n\n#### Converting Between Formats\nAll objects can be converted to/from most common geospatial formats.\n```python\nfrom geostructures import *\nfrom geostructures.collections import FeatureCollection\n\npolygon = GeoPolygon(\n [\n Coordinate(-0.116092, 51.509865), Coordinate(-0.111092, 51.509865), \n Coordinate(-0.113092, 51.506865), Coordinate(-0.116092, 51.509865)\n ]\n)\n\n# GeoJSON\npolygon.to_geojson()\npolygon.from_geojson( { a geojson object } )\n\n# Well-Known Text (WKT)\npolygon.to_wkt()\npolygon.from_wkt( '<a wkt polygon string>' )\n\n# Python Shapely\npolygon.to_shapely()\npolygon.from_shapely( a shapely polygon )\n\n# FastKML\nfrom fastkml import KML\nk = KML() # also works with fastkml.Folder\nk.append(polygon.to_fastkml_placemark())\npolygon.from_fastkml_placemark(k.features[0])\n\n# Collections (and Tracks) of shapes have additional supported formats\ncollection = FeatureCollection([polygon])\n\n# Creates a geopandas DataFrame\ncollection.to_geopandas()\ncollection.from_geopandas( a geopandas DataFrame )\n\n# Creates a GeoJSON FeatureCollection\ncollection.to_geojson()\ncollection.from_geojson( { a geojson featurecollection } )\n\n# Read/Write a FeatureCollection to an ESRI Shapefile\nfrom zipfile import ZipFile\nwith ZipFile('shapefile_name.zip', 'w') as zfile:\n collection.to_shapefile(zfile)\n\ncollection.from_shapefile('shapefile_name.zip')\n\n# Shapefiles may contain multiple feature layers, so you can control which ones get read \ncollection.from_shapefile('shapefile_name.zip', read_layers=['layer1', 'layer2'])\n```\n\n#### Bounding Shapes by Time\nAll shapes can be bound by time instants or intervals\n```python\nfrom datetime import datetime, timedelta\nfrom geostructures import *\nfrom geostructures.time import TimeInterval\n\n# Shapes can be bounded by a datetime to represent an instant in time\npoint_in_time = GeoPoint(Coordinate(-0.154092, 51.539865), dt=datetime(2020, 5, 1, 12))\n\n# Or they can be bounded by a time interval\nspan_of_time = GeoPoint(\n Coordinate(-0.155092, 51.540865), \n dt=TimeInterval(\n datetime(2020, 5, 1, 13),\n datetime(2020, 5, 1, 14)\n )\n)\n\n# Time spans can also be set after instantiation\nanother_point = GeoPoint(Coordinate(-0.154092, 51.539865))\nanother_point.set_dt(TimeInterval(datetime(2020, 5, 1, 16), datetime(2020, 5, 1, 17)))\n\n# You can buffer a time-bound shape with a timedelta\nanother_point.buffer_dt(timedelta(hours=6))\n\n# Or strip shape's time bounds\nanother_point.strip_dt()\n\n# Collections where all underlying shapes are time-bound can be represented as a Track, which\n# supports additional features\ntrack = Track([point_in_time, span_of_time])\n\n# Slice by datetime\nsubset = track[datetime(2020, 5, 1, 12):datetime(2020, 5, 1, 13)]\n\n# Get metrics between shapes\ntrack.centroid_distances # meters\ntrack.speed_diffs # meters per second\ntrack.time_start_diffs # timedeltas\n\n# Remove shapes that are spatially distant but chronologically close\ntrack.filter_impossible_journeys(max_speed=5) # meters per second\n```\n\n#### Geohashing\nGeostructures supports geohashing using both Uber's H3 and the original Niemeyer geohashing algorithm.\n```python\nfrom geostructures import *\nfrom geostructures.geohash import H3Hasher, NiemeyerHasher, h3_to_geopolygon, niemeyer_to_geobox\n\nbox = GeoBox(Coordinate(-0.154092, 51.539865), Coordinate(-0.140592, 51.505665))\ncircle = GeoCircle(Coordinate(-0.131092, 51.509865), radius=500)\ncollection = FeatureCollection([box, circle])\n\n# Create a H3 hasher\nhasher = H3Hasher(resolution=10)\n\n# Hash a singular shape to return the list of geohashes\nset_of_geohashes = hasher.hash_shape(box)\n\n# Convert the geohash into its corresponding GeoShape\ngeopolygon = h3_to_geopolygon(set_of_geohashes.pop())\n\n# Hash a collection of shapes to return a dictionary of { geohash: [ corresponding geoshapes ] }\n# or supply a custom aggregation function\nhashmap = hasher.hash_collection(collection)\n\n# Alternatively, hash using the Niemeyer algorithm\nhasher = NiemeyerHasher(length=8, base=16)\nset_of_geohashes = hasher.hash_shape(box)\ngeobox = niemeyer_to_geobox(set_of_geohashes.pop(), base=16)\nhashmap = hasher.hash_collection(collection)\n```\n\n#### Common Geospatial Calculations\n```python\nfrom geostructures import Coordinate\nfrom geostructures.calc import *\n\n# The straight-line direction of travel to get from point A to point B\nbearing_degrees(Coordinate(-0.154092, 51.539865), Coordinate(-0.140592, 51.505665))\n\n# The great-sphere distance between two points\nhaversine_distance_meters(Coordinate(-0.154092, 51.539865), Coordinate(-0.140592, 51.505665))\n\n# The resulting coordinate of traveling some distance from point A in a given direction\ninverse_haversine_degrees(\n Coordinate(-0.154092, 51.539865),\n 45, # degrees clockwise from true north\n 200 # the distance traveled (in meters)\n)\n\n# The same, except using radians for direction of travel\ninverse_haversine_radians(Coordinate(-0.154092, 51.539865), 0.7853981633974483, 200)\n\n# Rotate coordinates around a defined origin\nrotate_coordinates(\n [\n Coordinate(-0.154092, 51.539865), \n Coordinate(-0.140592, 51.505665)\n ],\n origin=Coordinate(-0.16, 50.24),\n degrees=45,\n)\n\n```\n\n### Projections\n\nThis library assumes that all geospatial terms and structures conform to the \n[WGS84 standard](https://en.wikipedia.org/wiki/World_Geodetic_System) (CRS 4326).\n\n### Sourcing\n\nThis library is designed to implement and extend the requirements of geospatial data laid out by:\n* [GML 3.1.1 PIDF-LO Shape Application Schema (PDF Download)](https://portal.ogc.org/files/?artifact_id=21630)\n* [RFC5491 (GEOPRIV PIDF-LO Usage)](https://www.rfc-editor.org/rfc/rfc5491.txt)\n* [RFC7946 (The GeoJSON Format)](https://datatracker.ietf.org/doc/html/rfc7946)\n* [ESRI Shapefile Technical Description - July 1998](http://www.esri.com/library/whitepapers/pdfs/shapefile.pdf)\n\n### Extensions / Related Projects\n\n**[Geochron](https://github.com/etalbert102/geochron)**\nA companion package to geostructures enabling geo-spatial-temporal data structures\n\n\n### Reporting Issues / Requesting Features\n\nThe Geostructures team uses Github issues to track development goals. Please be as descriptive \nas possible so we can effectively triage your request.\n\n### Contributing\n\nWe welcome all contributors! Please review [CONTRIBUTING.md](./CONTRIBUTING.md) for more information.\n\n### Developers\n\nCarl Best (Sr. Data Scientist/Project Owner)\\\nhttps://github.com/ccbest/\n\nEli Talbert (Sr. Data Scientist/PhD)\\\nhttps://github.com/etalbert102\n\nJessica Moore (Sr. Data Scientist)\\\nhttps://github.com/jessica-writes-code\n\nRichard Marshall (Data Scientist/SME)\\\nhttps://github.com/RichardMarshall13\n",
"bugtrack_url": null,
"license": null,
"summary": "A lightweight implementation of shapes drawn across a geo-temporal plane.",
"version": "0.12.2",
"project_urls": {
"Homepage": "https://github.com/ccbest/geostructures"
},
"split_keywords": [],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "c111ba8f03e21a0e897d2fd73dc9c157e880e73e3f2a4c170b7b2c1887b2460d",
"md5": "998d71ab75f26f8704dca783e7b5d89b",
"sha256": "b4543bdbd0ad4fd78505f81dce3f0d5b12cf957d7ece02062d6400a36c402b92"
},
"downloads": -1,
"filename": "geostructures-0.12.2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "998d71ab75f26f8704dca783e7b5d89b",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.6",
"size": 59693,
"upload_time": "2024-12-08T15:27:28",
"upload_time_iso_8601": "2024-12-08T15:27:28.988861Z",
"url": "https://files.pythonhosted.org/packages/c1/11/ba8f03e21a0e897d2fd73dc9c157e880e73e3f2a4c170b7b2c1887b2460d/geostructures-0.12.2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "625222e793e151f065bd02d4254996ac266ef34e10c913c179db0c785979e93e",
"md5": "8ee0c4e10daa9043fee1fd8097577906",
"sha256": "eb7ee5923b48f5f413e562e5232157514922eb7156fb9097a9f88bc7bf685a79"
},
"downloads": -1,
"filename": "geostructures-0.12.2.tar.gz",
"has_sig": false,
"md5_digest": "8ee0c4e10daa9043fee1fd8097577906",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.6",
"size": 78142,
"upload_time": "2024-12-08T15:27:30",
"upload_time_iso_8601": "2024-12-08T15:27:30.971303Z",
"url": "https://files.pythonhosted.org/packages/62/52/22e793e151f065bd02d4254996ac266ef34e10c913c179db0c785979e93e/geostructures-0.12.2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2024-12-08 15:27:30",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "ccbest",
"github_project": "geostructures",
"travis_ci": false,
"coveralls": true,
"github_actions": true,
"requirements": [
{
"name": "numpy",
"specs": [
[
"<",
"2"
],
[
">=",
"1"
]
]
}
],
"lcname": "geostructures"
}