GithubHelp home page GithubHelp logo

cleder / pygeoif Goto Github PK

View Code? Open in Web Editor NEW
57.0 6.0 25.0 776 KB

Basic implementation of the __geo_interface__ ๐ŸŒ๏ธ

Home Page: https://pygeoif.readthedocs.io

Python 100.00%
gis geospatial geojson hypothesis

pygeoif's Introduction

Introduction

PyGeoIf provides a GeoJSON-like protocol for geo-spatial (GIS) vector data.

Other Python programs and packages that you may have heard of that implement this protocol:

When you want to write your own geospatial library with support for this protocol you may use pygeoif as a starting point and build your functionality on top of it. It has no requirements outside the Python standard library and is therefore easy to integrate into your project. It is tested on CPython and PyPy, but it should work on alternative Python implementations (that implement the language specification >=3.8) as well.

You may think of pygeoif as a 'shapely ultralight' which lets you construct geometries and perform very basic operations like reading and writing geometries from/to WKT, constructing line strings out of points, polygons from linear rings, multi polygons from polygons, etc. It was inspired by shapely and implements the geometries in a way that when you are familiar with pygeoif, you will feel right at home with shapely or the other way round. It provides Hypothesis strategies for all geometries for property based testing with Hypothesis.

It was written to provide clean and python only geometries for fastkml

Documentation GitHub Actions Codecov Tested with Hypothesis Black Mypy Openhub CodeFactor pre-commit Supported Python versions Supported Python implementations Latest Version License Downloads

Installation

You can install PyGeoIf from pypi using pip:

pip install pygeoif

Example

>>> from pygeoif import geometry
>>> p = geometry.Point(1,1)
>>> p.__geo_interface__
{'type': 'Point', 'bbox': (1, 1, 1, 1), 'coordinates': (1, 1)}
>>> print(p)
POINT (1 1)
>>> p
Point(1, 1)
>>> l = geometry.LineString([(0.0, 0.0), (1.0, 1.0)])
>>> l.bounds
(0.0, 0.0, 1.0, 1.0)
>>> print(l)
LINESTRING (0.0 0.0, 1.0 1.0)

You find more examples in the tests directory which cover every aspect of pygeoif or in fastkml.

Classes

All classes implement the attribute:

  • __geo_interface__: as discussed above, an interface to GeoJSON.

All geometry classes implement the attributes:

  • geom_type: Returns a string specifying the Geometry Type of the object
  • bounds: Returns a (minx, miny, maxx, maxy) tuple that bounds the object.
  • wkt: Returns the 'Well Known Text' representation of the object

For two-dimensional geometries the following methods are implemented:

  • convex_hull: Returns a representation of the smallest convex Polygon containing all the points in the object unless the number of points in the object is less than three. For two points, the convex hull collapses to a LineString; for 1, a Point. For three dimensional objects only their projection in the xy plane is taken into consideration. Empty objects without coordinates return None for the convex_hull.

Point

A zero dimensional geometry

A point has zero length and zero area. A point cannot be empty.

Attributes

x, y, z : float
Coordinate values

Example

LineString

A one-dimensional figure comprising one or more line segments

A LineString has non-zero length and zero area. It may approximate a curve and need not be straight. Unlike a LinearRing, a LineString is not closed.

Attributes

geoms : sequence
A sequence of Points

LinearRing

A closed one-dimensional geometry comprising one or more line segments

A LinearRing that crosses itself or touches itself at a single point is invalid and operations on it may fail.

A LinearRing is self closing.

Polygon

A two-dimensional figure bounded by a linear ring

A polygon has a non-zero area. It may have one or more negative-space "holes" which are also bounded by linear rings. If any rings cross each other, the geometry is invalid and operations on it may fail.

Attributes

exterior : LinearRing
The ring which bounds the positive space of the polygon.
interiors : sequence
A sequence of rings which bound all existing holes.
maybe_valid: boolean
When a polygon has obvious problems such as self crossing lines or holes that are outside the exterior bounds this will return False. Even if this returns True the geometry may still be invalid, but if this returns False you do have a problem.

MultiPoint

A collection of one or more points.

Attributes

geoms : sequence
A sequence of Points.

MultiLineString

A collection of one or more line strings.

A MultiLineString has non-zero length and zero area.

Attributes

geoms : sequence
A sequence of LineStrings

MultiPolygon

A collection of one or more polygons.

Attributes

geoms : sequence
A sequence of Polygon instances

GeometryCollection

A heterogenous collection of geometries (Points, LineStrings, LinearRings and Polygons).

Attributes

geoms : sequence
A sequence of geometry instances

Please note: GEOMETRYCOLLECTION isn't supported by the Shapefile or GeoJSON format. And this sub-class isn't generally supported by ordinary GIS sw (viewers and so on). So it's very rarely used in the real GIS professional world.

Example

>>> from pygeoif import geometry
>>> p = geometry.Point(1.0, -1.0)
>>> p2 = geometry.Point(1.0, -1.0)
>>> geoms = [p, p2]
>>> c = geometry.GeometryCollection(geoms)
>>> [geom for geom in geoms]
[Point(1.0, -1.0), Point(1.0, -1.0)]

Feature

Aggregates a geometry instance with associated user-defined properties.

Attributes

geometry : object
A geometry instance
properties : dict
A dictionary linking field keys with values associated with with geometry instance

Example

>>> from pygeoif import Point, Feature
>>> p = Point(1.0, -1.0)
>>> props = {'Name': 'Sample Point', 'Other': 'Other Data'}
>>> a = Feature(p, props)
>>> a.properties
{'Name': 'Sample Point', 'Other': 'Other Data'}
>>> a.properties['Name']
'Sample Point'

FeatureCollection

A heterogenous collection of Features

Attributes

features: sequence
A sequence of feature instances

Example

>>> from pygeoif import Point, Feature, FeatureCollection
>>> p = Point(1.0, -1.0)
>>> props = {'Name': 'Sample Point', 'Other': 'Other Data'}
>>> a = Feature(p, props)
>>> p2 = Point(1.0, -1.0)
>>> props2 = {'Name': 'Sample Point2', 'Other': 'Other Data2'}
>>> b = Feature(p2, props2)
>>> features = [a, b]
>>> c = FeatureCollection(features)
>>> [feature for feature in c]
[Feature(Point(1.0, -1.0), {'Name': 'Sample Point', 'Other': 'Other Data'},...]

Functions

shape

Create a pygeoif feature from an object that provides the __geo_interface__ or any GeoJSON compatible dictionary.

>>> from shapely.geometry import Point
>>> from pygeoif import geometry, shape
>>> shape(Point(0,0))
Point(0.0, 0.0)

from_wkt

Create a geometry from its WKT representation

>>> from pygeoif import from_wkt
>>> p = from_wkt('POINT (0 1)')
>>> print(p)
POINT (0.0 1.0)

signed_area

Return the signed area enclosed by a ring. A value >= 0 indicates a counter-clockwise oriented ring.

orient

Returns a copy of a polygon with exteriors and interiors in the right orientation.

if ccw is True than the exterior will be in counterclockwise orientation and the interiors will be in clockwise orientation, or the other way round when ccw is False.

box

Return a rectangular polygon with configurable normal vector.

mapping

Return the __geo_interface__ dictionary.

Development

Clone this repository, create a virtualenv with Python 3.8 or later with python3 -m venv .venv and activate it with source .venv/bin/activate.

Then install the requirements with pip install -e ".[dev]".

pre-commit

Install the pre-commit hook with:

pip install pre-commit
pre-commit install

and check the code with:

pre-commit run --all-files

Testing

Run the unit and static tests with:

pytest tests
pytest --doctest-glob="README.rst"
black pygeoif
ruff pygeoif
flake8 pygeoif
mypy pygeoif

Acknowledgments

The tests were improved with mutmut which discovered some nasty edge cases.

pygeoif's People

Contributors

asellappen avatar bpshaver avatar cleder avatar deepsource-autofix[bot] avatar dependabot[bot] avatar djrobin17 avatar dlim87 avatar ianlee1521 avatar johnpdees avatar jzmiller1 avatar mhaberler avatar mindflayer avatar normworthington avatar pre-commit-ci[bot] avatar steko avatar tpt avatar whisk avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

pygeoif's Issues

Wrong Multipolygon

Hi there, I am reading a shapefile with pyshp:

sf = shapefile.Reader(os.path.join(cwd, shapefile_name))

for i in range(3):
    print(pygeoif.as_shape(sf.shape(i)).wkt)

The print result is the following one, with three single Polygon:

POLYGON((10.225265936197262 52.55248772818054, 10.2245 52.5526, 10.239898977006918 52.587244203792714, 10.257158479683646 52.626074168135474, 10.852814795836748 53.96616570784997, 10.869259391910935 54.00316231726286, 10.8855 54.0397, 10.886403440289543 54.0395673568947, 13.6072 53.6401, 13.588449967690627 53.603121815044084, 12.878710662659484 52.20339775809922, 12.8599 52.1663, 10.225265936197262 52.55248772818054))
POLYGON((4.994589472083415 51.136294699609905, 4.99387 51.1364, 5.009205610686967 51.17282464256336, 5.024854148511461 51.20999253946237, 5.590074308333594 52.552485036916316, 5.605067138274774 52.58809551782692, 5.62111 52.6262, 5.621949238159022 52.626076986136376, 8.26066 52.2393, 8.24233115618 52.20101287449061, 7.570667393061582 50.79797430581851, 7.55335 50.7618, 4.994589472083415 51.136294699609905))
POLYGON((11.174866239143656 51.13628785575964, 11.1741 51.1364, 11.1904078402353 51.175130145676, 11.205089843196898 51.20999902501951, 11.770362674077559 52.55248820634584, 11.7850061463172 52.58726557752808, 11.8014 52.6262, 11.802253839101338 52.62607484358844, 14.4409 52.2393, 14.422065104984824 52.19995522778889, 13.751660545626997 50.79952721075059, 13.7336 50.7618, 11.174866239143656 51.13628785575964))

Now, I am using pygeoif to create a Multipolygon with these given Polygon:

m = pygeoif.MultiPolygon(sf.shapes())
print(m.wkt)

and the result is broken (missing comma separation).

MULTIPOLYGON(((10.225265936197262 52.55248772818054, 10.2245 52.5526, 10.239898977006918 52.587244203792714, 10.257158479683646 52.626074168135474, 10.852814795836748 53.96616570784997, 10.869259391910935 54.00316231726286, 10.8855 54.0397, 10.886403440289543 54.0395673568947, 13.6072 53.6401, 13.588449967690627 53.603121815044084, 12.878710662659484 52.20339775809922, 12.8599 52.1663, 10.225265936197262 52.55248772818054))((4.994589472083415 51.136294699609905, 4.99387 51.1364, 5.009205610686967 51.17282464256336, 5.024854148511461 51.20999253946237, 5.590074308333594 52.552485036916316, 5.605067138274774 52.58809551782692, 5.62111 52.6262, 5.621949238159022 52.626076986136376, 8.26066 52.2393, 8.24233115618 52.20101287449061, 7.570667393061582 50.79797430581851, 7.55335 50.7618, 4.994589472083415 51.136294699609905))((11.174866239143656 51.13628785575964, 11.1741 51.1364, 11.1904078402353 51.175130145676, 11.205089843196898 51.20999902501951, 11.770362674077559 52.55248820634584, 11.7850061463172 52.58726557752808, 11.8014 52.6262, 11.802253839101338 52.62607484358844, 14.4409 52.2393, 14.422065104984824 52.19995522778889, 13.751660545626997 50.79952721075059, 13.7336 50.7618, 11.174866239143656 51.13628785575964)))

If I add comma as separator, I get this:

MULTIPOLYGON(((10.225265936197262 52.55248772818054, 10.2245 52.5526, 10.239898977006918 52.587244203792714, 10.257158479683646 52.626074168135474, 10.852814795836748 53.96616570784997, 10.869259391910935 54.00316231726286, 10.8855 54.0397, 10.886403440289543 54.0395673568947, 13.6072 53.6401, 13.588449967690627 53.603121815044084, 12.878710662659484 52.20339775809922, 12.8599 52.1663, 10.225265936197262 52.55248772818054)),((4.994589472083415 51.136294699609905, 4.99387 51.1364, 5.009205610686967 51.17282464256336, 5.024854148511461 51.20999253946237, 5.590074308333594 52.552485036916316, 5.605067138274774 52.58809551782692, 5.62111 52.6262, 5.621949238159022 52.626076986136376, 8.26066 52.2393, 8.24233115618 52.20101287449061, 7.570667393061582 50.79797430581851, 7.55335 50.7618, 4.994589472083415 51.136294699609905)),((11.174866239143656 51.13628785575964, 11.1741 51.1364, 11.1904078402353 51.175130145676, 11.205089843196898 51.20999902501951, 11.770362674077559 52.55248820634584, 11.7850061463172 52.58726557752808, 11.8014 52.6262, 11.802253839101338 52.62607484358844, 14.4409 52.2393, 14.422065104984824 52.19995522778889, 13.751660545626997 50.79952721075059, 13.7336 50.7618, 11.174866239143656 51.13628785575964)))

Can't run test_factories.py

Trying to run test_factories.py throws an error:
"cannot import name 'factories' from 'pygeoif'"

Running factories.py in turn throws:
"ImportError: cannot import name 'GenericAlias' from partially initialized module 'types' (most likely due to a circular import)"

Pip 23.1 deprecation warning

Hello,

Thanks for all your work on this package.

I've noticed that that when installing in a fresh virtual environment I get the following warning:

DEPRECATION: fxpmath is being installed using the legacy 'setup.py install' method, because it does not have a 'pyproject.toml' and the 'wheel' package is not installed. pip 23.1 will enforce this behaviour change. A possible replacement is to enable the '--use-pep517' option. Discussion can be found at https://github.com/pypa/pip/issues/8559

It looks like pip 23.1 is going to be released in the near future, so I thought I'd bring it to your attention.

Thanks again!

Graham Scan as an alternative convex hull algorithm.

def orientation(p, q, r):
    """
    Calculate orientation of three points (p, q, r).
    Returns:
    -1 if counterclockwise
    0 if colinear
    1 if clockwise
    """
    val = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1])
    if val == 0:
        return 0
    return 1 if val > 0 else -1

def graham_scan(points):
    """
    Graham's scan algorithm to find the convex hull of a set of points.
    Returns the sorted points forming the convex hull.
    """
    n = len(points)
    if n < 3:
        return points

    # Find the point with the lowest y-coordinate (and leftmost if ties)
    pivot = min(points, key=lambda p: (p[1], p[0]))

    # Sort the points based on polar angle with respect to the pivot
    sorted_points = sorted(points, key=lambda p: (math.atan2(p[1]-pivot[1], p[0]-pivot[0]), p))

    # Initialize the stack
    stack = [pivot, sorted_points[0], sorted_points[1]]

    # Iterate over the sorted points
    for i in range(2, n):
        while len(stack) > 1 and orientation(stack[-2], stack[-1], sorted_points[i]) != -1:
            stack.pop()
        stack.append(sorted_points[i])

    return stack

# Example: Collection of arbitrary points
points = [(0, 0), (3, 1), (1, 2), (2, 3), (4, 4), (3, 0), (2, 1)]

# Sort the points to form a closed linear ring
sorted_ring = graham_scan(points)

print("Original Points:", points)
print("Sorted Linear Ring Points:", sorted_ring)

This implementation uses the Graham's scan algorithm to find the convex hull of the points, and the resulting sorted_ring contains the points forming a closed linear ring without self-intersections.

Initial Update

The bot created this issue to inform you that pyup.io has been set up on this repo.
Once you have closed it, the bot will open pull requests for updates as soon as they are available.

Properties, CRS and other optional geo_interface items

I might have overlooked it but it seems like the as_shape function and geometry classes don't take advantage of any of the optional geo_interface keys (and that the geo_interface specification itself omits the crs key in the GeoJSON standard). Could you provide some detail on why these were omitted? Are these things that you felt that pygeoif users should implement themselves?

Would pull requests that implemented them be looked upon favorably or at least be something you would be interested in reviewing and discussing?

Thanks for your work on pygeoif!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.