Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Tracker = "https://github.com/polusai/microjson/issues"
ipykernel = "^6.27.1"

[tool.poetry.dependencies]
python = ">=3.9.15,<3.14"
python = ">=3.11,<3.14"
pydantic = "^2.3.0"
geojson-pydantic = "^1.2.0"
geojson2vt = "^1.0.1"
Expand Down
File renamed without changes.
File renamed without changes.
119 changes: 63 additions & 56 deletions src/microjson/microjson2vt/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@

import math
from abc import ABC, abstractmethod
from .simplify import simplify
from .feature import Slice, create_feature

# converts Microjson feature into an intermediate projected JSON vector format
Expand All @@ -17,12 +16,14 @@ def convert(data, options):
"""
wrapper around AbstractProjector.convert
"""
projector = options.get('projector')
bounds = options.get('bounds')
projector = options.get("projector")
bounds = options.get("bounds")
if projector is None:
projector = CartesianProjector(
options.get('bounds')) if bounds is not None else \
MercatorProjector()
projector = (
CartesianProjector(options.get("bounds"))
if bounds is not None
else MercatorProjector()
)

return projector.convert(data, options)

Expand All @@ -33,6 +34,7 @@ class AbstractProjector(ABC):
Concrete classes should implement the project_x and project_y methods.

"""

def __init__(self, bounds=None):
self.bounds = bounds

Expand All @@ -46,88 +48,92 @@ def project_y(self, y):

def convert(self, data, options):
features = []
if data.get('type') == 'FeatureCollection':
for i in range(len(data.get('features'))):
self.convert_feature(
features,
data.get('features')[i],
options, i)
if data.get("type") == "FeatureCollection":
for i in range(len(data.get("features"))):
self.convert_feature(features, data.get("features")[i], options, i)
# check that geometry is not empty
if len(features[-1].get('geometry')) == 0:
if len(features[-1].get("geometry")) == 0:
# remove feature with index i
features.pop()

elif data.get('type') == 'Feature':
elif data.get("type") == "Feature":
self.convert_feature(features, data, options)
else:
# single geometry or a geometry collection
self.convert_feature(features, {"geometry": data}, options)
return features

def convert_feature(self, features, geojson, options, index=None):
if geojson.get('geometry', None) is None:
if geojson.get("geometry", None) is None:
return

coords = geojson.get('geometry').get('coordinates')
coords = geojson.get("geometry").get("coordinates")

if coords is not None and len(coords) == 0:
return

type_ = geojson.get('geometry').get('type')
tolerance = math.pow(options.get(
'tolerance') / ((1 << options.get('maxZoom')) * options.get(
'extent')), 2)
type_ = geojson.get("geometry").get("type")
tolerance = math.pow(
options.get("tolerance")
/ ((1 << options.get("maxZoom")) * options.get("extent")),
2,
)
geometry = Slice([])
id_ = geojson.get('id')
if options.get('promoteId', None) is not None and geojson.get(
'properties', None) is not None and 'promoteId' in geojson.get(
'properties'):
id_ = geojson['properties'][options.get('promoteId')]
elif options.get('generateId', False):
id_ = geojson.get("id")
if (
options.get("promoteId", None) is not None
and geojson.get("properties", None) is not None
and "promoteId" in geojson.get("properties")
):
id_ = geojson["properties"][options.get("promoteId")]
elif options.get("generateId", False):
id_ = index if index is not None else 0

if type_ == 'Point':
if type_ == "Point":
self.convert_point(coords, geometry)
elif type_ == 'MultiPoint':
elif type_ == "MultiPoint":
for p in coords:
self.convert_point(p, geometry)
elif type_ == 'LineString':
elif type_ == "LineString":
self.convert_line(coords, geometry, tolerance, False)
elif type_ == 'MultiLineString':
if options.get('lineMetrics'):
elif type_ == "MultiLineString":
if options.get("lineMetrics"):
# explode into linestrings to be able to track metrics
for line in coords:
geometry = Slice([])
self.convert_line(line, geometry, tolerance, False)
features.append(
create_feature(
id_,
'LineString',
geometry,
geojson.get('properties')))
id_, "LineString", geometry, geojson.get("properties")
)
)
return
else:
self.convert_lines(coords, geometry, tolerance, False)
elif type_ == 'Polygon':
elif type_ == "Polygon":
self.convert_lines(coords, geometry, tolerance, True)
elif type_ == 'MultiPolygon':
elif type_ == "MultiPolygon":
for polygon in coords:
newPolygon = []
self.convert_lines(polygon, newPolygon, tolerance, True)
geometry.append(newPolygon)
elif type_ == 'GeometryCollection':
for singleGeometry in geojson['geometry']['geometries']:
self.convert_feature(features, {
"id": str(id_),
"geometry": singleGeometry,
"properties": geojson.get('properties')
}, options, index)
elif type_ == "GeometryCollection":
for singleGeometry in geojson["geometry"]["geometries"]:
self.convert_feature(
features,
{
"id": str(id_),
"geometry": singleGeometry,
"properties": geojson.get("properties"),
},
options,
index,
)
return
else:
raise Exception('Input data is not a valid GeoJSON object.')
raise Exception("Input data is not a valid GeoJSON object.")

features.append(create_feature(
id_, type_, geometry, geojson.get('properties')))
features.append(create_feature(id_, type_, geometry, geojson.get("properties")))

def convert_point(self, coords, out):
out.append(self.project_x(coords[0]))
Expand All @@ -151,7 +157,8 @@ def convert_line(self, ring, out, tolerance, isPolygon):
size += (x0 * y - x * y0) / 2 # area
else:
size += math.sqrt(
math.pow(x - x0, 2) + math.pow(y - y0, 2)) # length
math.pow(x - x0, 2) + math.pow(y - y0, 2)
) # length
x0 = x
y0 = y

Expand Down Expand Up @@ -187,13 +194,13 @@ def project_y(self, y):

class MercatorProjector(AbstractProjector):
def project_x(self, x):
return x / 360. + 0.5
return x / 360.0 + 0.5

def project_y(self, y):
sin = math.sin(y * math.pi / 180.)
if sin == 1.:
return 0.
if sin == -1.:
return 1.
y2 = 0.5 - 0.25 * math.log((1. + sin) / (1. - sin)) / math.pi
return 0 if y2 < 0. else (1. if y2 > 1. else y2)
sin = math.sin(y * math.pi / 180.0)
if sin == 1.0:
return 0.0
if sin == -1.0:
return 1.0
y2 = 0.5 - 0.25 * math.log((1.0 + sin) / (1.0 - sin)) / math.pi
return 0 if y2 < 0.0 else (1.0 if y2 > 1.0 else y2)
Loading