forked from jazzband/geojson
-
Notifications
You must be signed in to change notification settings - Fork 0
/
geometry.py
executable file
·150 lines (111 loc) · 4.25 KB
/
geometry.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
from decimal import Decimal
from numbers import Number
from geojson.base import GeoJSON
DEFAULT_PRECISION = 6
class Geometry(GeoJSON):
"""
Represents an abstract base class for a WGS84 geometry.
"""
def __init__(self, coordinates=None, validate=False, precision=None, **extra):
"""
Initialises a Geometry object.
:param coordinates: Coordinates of the Geometry object.
:type coordinates: tuple or list of tuple
:param validate: Raise exception if validation errors are present?
:type validate: boolean
:param precision: Number of decimal places for lat/lon coords.
:type precision: integer
"""
super().__init__(**extra)
if precision is None:
precision = DEFAULT_PRECISION
self["coordinates"] = self.clean_coordinates(
coordinates or [], precision)
if validate:
errors = self.errors()
if errors:
raise ValueError(f'{errors}: {coordinates}')
@classmethod
def clean_coordinates(cls, coords, precision):
if isinstance(coords, cls):
return coords['coordinates']
new_coords = []
if isinstance(coords, Geometry):
coords = [coords]
for coord in coords:
if isinstance(coord, (list, tuple)):
new_coords.append(cls.clean_coordinates(coord, precision))
elif isinstance(coord, Geometry):
new_coords.append(coord['coordinates'])
elif isinstance(coord, (float, int, Decimal)):
new_coords.append(round(coord, precision))
else:
raise ValueError("%r is not a JSON compliant number" % coord)
return new_coords
class GeometryCollection(GeoJSON):
"""
Represents an abstract base class for collections of WGS84 geometries.
"""
def __init__(self, geometries=None, **extra):
super().__init__(**extra)
self["geometries"] = geometries or []
def errors(self):
errors = [geom.errors() for geom in self['geometries']]
return [err for err in errors if err]
def __getitem__(self, key):
try:
return self.get("geometries", ())[key]
except (KeyError, TypeError, IndexError):
return super(GeoJSON, self).__getitem__(key)
# Marker classes.
def check_point(coord):
if not isinstance(coord, list):
return 'each position must be a list'
if len(coord) not in (2, 3):
return 'a position must have exactly 2 or 3 values'
for number in coord:
if not isinstance(number, Number):
return 'a position cannot have inner positions'
class Point(Geometry):
def errors(self):
return check_point(self['coordinates'])
class MultiPoint(Geometry):
def errors(self):
return self.check_list_errors(check_point, self['coordinates'])
def check_line_string(coord):
if not isinstance(coord, list):
return 'each line must be a list of positions'
if len(coord) < 2:
return ('the "coordinates" member must be an array of '
'two or more positions')
for pos in coord:
error = check_point(pos)
if error:
return error
class LineString(MultiPoint):
def errors(self):
return check_line_string(self['coordinates'])
class MultiLineString(Geometry):
def errors(self):
return self.check_list_errors(check_line_string, self['coordinates'])
def check_polygon(coord):
if not isinstance(coord, list):
return 'Each polygon must be a list of linear rings'
if not all(isinstance(elem, list) for elem in coord):
return "Each element of a polygon's coordinates must be a list"
lengths = all(len(elem) >= 4 for elem in coord)
if lengths is False:
return 'Each linear ring must contain at least 4 positions'
isring = all(elem[0] == elem[-1] for elem in coord)
if isring is False:
return 'Each linear ring must end where it started'
class Polygon(Geometry):
def errors(self):
return check_polygon(self['coordinates'])
class MultiPolygon(Geometry):
def errors(self):
return self.check_list_errors(check_polygon, self['coordinates'])
class Default:
"""
GeoJSON default object.
"""