geostructures


Namegeostructures JSON
Version 0.12.2 PyPI version JSON
download
home_pagehttps://github.com/ccbest/geostructures
SummaryA lightweight implementation of shapes drawn across a geo-temporal plane.
upload_time2024-12-08 15:27:30
maintainerNone
docs_urlNone
authorCarl Best
requires_python>=3.6
licenseNone
keywords
VCS
bugtrack_url
requirements numpy
Travis-CI No Travis.
coveralls test coverage
            
# Geostructures

[![Unit Tests](https://github.com/ccbest/geostructures/actions/workflows/unit-tests.yml/badge.svg)](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[![Unit Tests](https://github.com/ccbest/geostructures/actions/workflows/unit-tests.yml/badge.svg)](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"
}
        
Elapsed time: 0.34718s