Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Do not modify source data, include geojson from file and compute bbox consistently #139

Merged
merged 11 commits into from
Sep 12, 2021
96 changes: 71 additions & 25 deletions docs/example/input-types.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,61 @@ tp.Topology(gdf, prequantize=False).to_json()
</div>
</div>

* * *

## GeoJSON data from file
A GeoJSON file can be parsed as json dictionary.

<div class="code-example mx-1 bg-example">
<div class="example-label" markdown="1">
Example 🔧
{: .label .label-blue-000 }
</div>
<div class="example-text" markdown="1">

```python
import topojson as tp
import json

with open("tests/files_topojson/example_data_africa.geojson", 'r') as f:
data = json.load(f)

assert data['type']) == 'FeatureCollection'
topo = topojson.Topology(data)

# to visualise we use the (optional!) package Altair.
topo.toposimplify(4).to_alt()
```
<div id="embed_output_mesh_altair_from_geojson"></div>
</div>
</div>

* * *

## TopoJSON data from file
A TopoJSON file can be postprocessed using json dictionary.

<div class="code-example mx-1 bg-example">
<div class="example-label" markdown="1">
Example 🔧
{: .label .label-blue-000 }
</div>
<div class="example-text" markdown="1">

```python
import topojson as tp
import json

with open("tests/files_topojson/naturalearth_lowres_africa.topojson", 'r') as f:
data = json.load(f)
# parse topojson file using `object_name`
topo = topojson.Topology(data, object_name="data")
topo.toposimplify(4).to_svg()
```
<img src="../images/africa_toposimp.svg">
</div>
</div>

* * *


Expand Down Expand Up @@ -311,28 +366,19 @@ tp.Topology(dict_in, prequantize=False).to_json()
</div>
</div>

* * *

## TopoJSON file loaded as json-dict
A TopoJSON file can be postprocessed.

<div class="code-example mx-1 bg-example">
<div class="example-label" markdown="1">
Example 🔧
{: .label .label-blue-000 }
</div>
<div class="example-text" markdown="1">

```python
import topojson as tp
import json

with open("tests/files_topojson/naturalearth_lowres_africa.topojson", 'r') as f:
data = json.load(f)
# parse topojson file using `object_name`
topo = topojson.Topology(data, object_name="data")
topo.toposimplify(4).to_svg()
```
<img src="../images/africa_toposimp.svg">
</div>
</div>
<script>
window.addEventListener("DOMContentLoaded", event => {
var opt = {
mode: "vega-lite",
renderer: "svg",
actions: false
};

var spec_mesh_altair = "{{site.baseurl}}/json/example_mesh.vl.json";
vegaEmbed("#embed_output_mesh_altair_from_geojson", spec_mesh_altair, opt).catch(console.err);

});
</script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vega@5"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vega-lite@4"></script>
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
2,061 changes: 2,061 additions & 0 deletions tests/files_geojson/example_data_africa.geojson

Large diffs are not rendered by default.

72 changes: 55 additions & 17 deletions tests/test_extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from shapely import geometry
import geopandas
import geojson
from geojson import Feature, Polygon, FeatureCollection
import fiona


Expand Down Expand Up @@ -361,18 +362,20 @@ def test_extract_fiona_file_gpkg():

assert len(topo["bookkeeping_geoms"]) == 4


# test to check if original data is not modified
def test_extract_dict_org_data_untouched():
data = {
"foo": {"type": "LineString", "coordinates": [[0, 0], [1, 0], [2, 0]]},
"bar": {"type": "LineString", "coordinates": [[0, 0], [1, 0], [2, 0]]},
}
topo = Extract(data).to_dict()
topo_foo = topo['objects']['foo']
topo_foo = topo["objects"]["foo"]
data_foo = data["foo"]

assert 'arcs' in topo_foo.keys()
assert 'arcs' not in data_foo.keys()
assert "arcs" in topo_foo.keys()
assert "arcs" not in data_foo.keys()


# test to check if original data is not modified
def test_extract_list_org_data_untouched():
Expand All @@ -381,41 +384,76 @@ def test_extract_list_org_data_untouched():
geometry.Polygon([[1, 0], [2, 0], [2, 1], [1, 1], [1, 0]]),
]
topo = Extract(data).to_dict()
topo_0 = topo['objects'][0]
topo_0 = topo["objects"][0]
data_0 = data[0]

assert 'arcs' in topo_0.keys()
assert data_0.type == 'Polygon'
assert "arcs" in topo_0.keys()
assert data_0.type == "Polygon"


# test to check if original data is not modified
def test_extract_gdf_org_data_untouched():
data = geopandas.read_file(
"tests/files_geojson/naturalearth_alb_grc.geojson", driver="GeoJSON"
)
topo = Extract(data).to_dict()
topo_0 = topo['objects'][0]
topo_0 = topo["objects"][0]
data_0 = data.iloc[0]

assert 'arcs' in topo_0.keys()
assert data_0.geometry.type == 'Polygon'
assert "arcs" in topo_0.keys()
assert data_0.geometry.type == "Polygon"


# test to check if original data is not modified
def test_extract_shapely_org_data_untouched():
data = geometry.LineString([[0, 0], [1, 0], [1, 1], [0, 1]])
topo = Extract(data).to_dict()
topo_0 = topo['objects'][0]
topo = Extract(data).to_dict()
topo_0 = topo["objects"][0]

assert "arcs" in topo_0.keys()
assert data.type == "LineString"

assert 'arcs' in topo_0.keys()
assert data.type == 'LineString'

# test to check if original data is not modified
def test_extract_shapefile_org_data_untouched():
import shapefile

data = shapefile.Reader("tests/files_shapefile/southamerica.shp")
topo = Extract(data).to_dict()
topo_0 = topo['objects']['feature_00']['geometries'][0]
data_0 = data.__geo_interface__['features'][0]['geometry']
topo_0 = topo["objects"]["feature_00"]["geometries"][0]
data_0 = data.__geo_interface__["features"][0]["geometry"]

assert "arcs" in topo_0.keys()
assert "arcs" not in data_0.keys()


# issue 137 do not modify source data
def test_extract_source_data_modify():
# prepare data
feat_1 = Feature(
geometry=Polygon([[[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]]]),
properties={"name": "abc"},
)
feat_2 = Feature(
geometry=Polygon([[[1, 0], [2, 0], [2, 1], [1, 1], [1, 0]]]),
properties={"name": "def"},
)
data = FeatureCollection([feat_1, feat_2])

# before Topology()
assert "geometry" in data["features"][0].keys()

# apply Topology()
topo = Extract(data)

# after Topology()
assert "geometry" in data["features"][0].keys()


# why cannot load geojson file using json module?
def test_extract_read_geojson_from_json_dict():
with open("tests/files_geojson/naturalearth_lowres.geojson") as f:
data = json.load(f)
topo = Extract(data).to_dict()

assert 'arcs' in topo_0.keys()
assert 'arcs' not in data_0.keys()
assert len(topo["linestrings"]) == 287
61 changes: 40 additions & 21 deletions tests/test_topology.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,58 +423,65 @@ def test_topology_geodataframe_valid():

def test_topology_geojson_duplicates():

p0 = wkt.loads('POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0))')
p1 = wkt.loads('POLYGON ((0 1, 0 2, 1 2, 1 1, 0 1))')
p2 = wkt.loads('POLYGON ((1 0, 2 0, 2 -1, 1 -1, 1 0))')
data = geopandas.GeoDataFrame({"name": ["abc", "def", "ghi"], "geometry": [p0, p1, p2]})
p0 = wkt.loads("POLYGON ((0 0, 0 1, 1 1, 2 1, 2 0, 1 0, 0 0))")
p1 = wkt.loads("POLYGON ((0 1, 0 2, 1 2, 1 1, 0 1))")
p2 = wkt.loads("POLYGON ((1 0, 2 0, 2 -1, 1 -1, 1 0))")
data = geopandas.GeoDataFrame(
{"name": ["abc", "def", "ghi"], "geometry": [p0, p1, p2]}
)
topo = topojson.Topology(data, prequantize=False)
p0_wkt = topo.to_gdf().geometry[0].wkt

assert p0_wkt == 'POLYGON ((0 1, 0 0, 1 0, 2 0, 2 1, 1 1, 0 1))'
assert p0_wkt == "POLYGON ((0 1, 0 0, 1 0, 2 0, 2 1, 1 1, 0 1))"


# test for https://github.com/mattijn/topojson/issues/110
def test_topology_topoquantization_dups():
gdf = geopandas.read_file(geopandas.datasets.get_path("naturalearth_lowres"))
gdf = gdf[gdf.name.isin(['France', 'Belgium', 'Netherlands'])]
gdf = gdf[gdf.name.isin(["France", "Belgium", "Netherlands"])]
topo = topojson.Topology(data=gdf, prequantize=False).toposimplify(4)
topo = topo.topoquantize(50).to_dict()

assert topo['arcs'][6] == [[44, 47], [0, 0]]

assert topo["arcs"][6] == [[44, 47], [0, 0]]


# parse topojson from file
def test_topology_topojson_from_file():
with open("tests/files_topojson/naturalearth.topojson", 'r') as f:
with open("tests/files_topojson/naturalearth.topojson", "r") as f:
data = json.load(f)

topo = topojson.Topology(data).to_dict()

assert len(topo["objects"]) == 1
assert len(topo["objects"]) == 1


# parse topojson file and plot with altair
def test_topology_topojson_to_alt():
# load topojson file into dict
with open("tests/files_topojson/naturalearth_lowres_africa.topojson", 'r') as f:
with open("tests/files_topojson/naturalearth_lowres_africa.topojson", "r") as f:
data = json.load(f)

# parse topojson file using `object_name`
topo = topojson.Topology(data, object_name="data")
# apply toposimplify and serialize to altair
chart = topo.toposimplify(1).to_alt()
chart = topo.toposimplify(1).to_alt()

assert len(chart.__dict__.keys()) == 2


# Object of type int64 is not JSON serializable
def test_topology_topojson_to_alt_int64():
# load topojson file into dict
with open("tests/files_topojson/mesh2d.topojson", 'r') as f:
data = json.load(f)
with open("tests/files_topojson/mesh2d.topojson", "r") as f:
data = json.load(f)

# parse topojson file using `object_name`
topo = topojson.Topology(data, object_name="mesh2d_flowelem_bl")
# apply toposimplify and serialize to altair
chart = topo.toposimplify(1).to_alt()
chart = topo.toposimplify(1).to_alt()

assert len(chart.__dict__.keys()) == 2

assert len(chart.__dict__.keys()) == 2

def test_topology_nested_list_properties():
from geojson import Feature, Polygon, FeatureCollection
Expand Down Expand Up @@ -516,12 +523,24 @@ def test_topology_nested_list_properties():

assert len(topo) == 4


def test_topology_update_bbox_topoquantize_toposimplify():
# load example data representing continental Africa
data = topojson.utils.example_data_africa()
data = topojson.utils.example_data_africa()
# compute the topology
topo = topojson.Topology(data)
topo = topojson.Topology(data)
# apply simplification on the topology and render as SVG
bbox = topo.topoquantize(10).to_dict()['bbox']
bbox = topo.topoquantize(10).to_dict()["bbox"]

assert round(bbox[0], 1) == -17.6


def test_topology_bbox_no_delta_transform():
data = {
"foo": {"type": "LineString", "coordinates": [[0, 0], [1, 0], [2, 0]]},
"bar": {"type": "LineString", "coordinates": [[0, 0], [1, 0], [2, 0]]},
}
topo_1 = topojson.Topology(data, object_name="topo_1").to_dict()
topo_2 = topojson.Topology(topo_1, object_name="topo_1").to_dict()

assert bbox == (0, 0, 9, 9)
assert topo_1["bbox"] == topo_2["bbox"]
4 changes: 2 additions & 2 deletions topojson/core/dedup.py
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,7 @@ def _pop_merged_arcs(self, bk_dups, linestring_list, array_bk):

# collect new indices of shared arcs
u, c = np.unique(arr_new, return_counts=True)
arr_bk_sarcs = u[c > 1]
arr_bk_sarcs = u[c > 1]
arr_bk_sarcs = arr_bk_sarcs[~np.isnan(arr_bk_sarcs)]

return arr_new, arr_bk_sarcs
11 changes: 10 additions & 1 deletion topojson/core/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,10 +447,12 @@ def _extract_featurecollection(self, geom):

# convert FeatureCollection into a dict of features
if not hasattr(self, "_obj"):
geom = copy.deepcopy(geom)
obj = geom
self._obj = geom
else:
obj = self._obj

data = {}
zfill_value = len(str(len(obj["features"])))

Expand All @@ -460,7 +462,9 @@ def _extract_featurecollection(self, geom):
feature["type"] = "GeometryCollection"
feature["geometries"] = [feature["geometry"]]
feature.pop("geometry", None)
data["feature_{}".format(str(idx).zfill(zfill_value))] = feature
data["feature_{}".format(str(idx).zfill(zfill_value))] = geometry.shape(
feature
) # feature

# new data dictionary is created, throw the geometries back to main()
self._is_single = False
Expand Down Expand Up @@ -599,6 +603,11 @@ def _extract_dictionary(self, geom):
"""

self._is_single = False
if (
"type" in geom.keys()
and geom["type"].casefold() == "FeatureCollection".casefold()
):
return self._extract_featurecollection(geom)
self._data = copy.deepcopy(self._data)

# iterate over the input dictionary or geographical object
Expand Down
Loading