mirror of
https://github.com/felt/tippecanoe.git
synced 2026-10-02 08:25:40 +02:00
Merge remote-tracking branch 'origin/main' into tile-join-sqlite
This commit is contained in:
@@ -1,3 +1,38 @@
|
||||
# 2.71.0
|
||||
|
||||
* Add --clip-bounding-box and --clip-polygon options to tippecanoe-overzoom
|
||||
|
||||
# 2.70.1
|
||||
|
||||
* Raise tippecanoe-decode limit on the size of individual tiles
|
||||
|
||||
# 2.70.0
|
||||
|
||||
* Performance improvements to tippecanoe-overzoom with attribute exclusion
|
||||
|
||||
# 2.69.0
|
||||
|
||||
* Fix crash when the first bin gets clipped away
|
||||
|
||||
# 2.68.0
|
||||
|
||||
* Adds `--no-tile-compression` option to `tippecanoe-overzoom`
|
||||
* Make `tippecanoe-overzoom` clip the output tile to the tile buffer after assigning points to bins
|
||||
* Adds `--include`/`-y` option to `tippecanoe-decode` to decode only specified attributes
|
||||
* Cleans up some inconsistent handling of variable tile extents in overzooming code
|
||||
* Speeds up overzooming slightly in `tile-join` by doing less preflighting to discover which child tiles contain features
|
||||
|
||||
# 2.67.0
|
||||
|
||||
* Reduce memory consumption of duplicate attribute names in `serial_feature`
|
||||
* The maxzoom guess calculation now takes into account the number of duplicate feature locations
|
||||
|
||||
# 2.66.0
|
||||
|
||||
* Only bin by ID, not by geometry, if --bin-by-id-list is specified
|
||||
* Do attribute accumulation in overzoom in mvt_value instead of converting to serial_val
|
||||
* Fix bool values read from flatgeobuf sources (#289)
|
||||
|
||||
# 2.65.0
|
||||
|
||||
* Improve spatial distribution of --retain-points-multiplier features
|
||||
|
||||
@@ -56,10 +56,10 @@ PG=
|
||||
H = $(wildcard *.h) $(wildcard *.hpp)
|
||||
C = $(wildcard *.c) $(wildcard *.cpp)
|
||||
|
||||
INCLUDES = -I/usr/local/include -I.
|
||||
INCLUDES = -I/usr/local/include -I. -Iclipper2/include
|
||||
LIBS = -L/usr/local/lib
|
||||
|
||||
tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o serial.o main.o text.o dirtiles.o pmtiles_file.o plugin.o read_json.o write_json.o geobuf.o flatgeobuf.o evaluator.o geocsv.o csv.o geojson-loop.o json_logger.o visvalingam.o compression.o clip.o sort.o attribute.o thread.o shared_borders.o
|
||||
tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o serial.o main.o text.o dirtiles.o pmtiles_file.o plugin.o read_json.o write_json.o geobuf.o flatgeobuf.o evaluator.o geocsv.o csv.o geojson-loop.o json_logger.o visvalingam.o compression.o clip.o sort.o attribute.o thread.o shared_borders.o clipper2/src/clipper.engine.o
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
|
||||
|
||||
tippecanoe-enumerate: enumerate.o
|
||||
@@ -68,16 +68,16 @@ tippecanoe-enumerate: enumerate.o
|
||||
tippecanoe-decode: decode.o projection.o mvt.o write_json.o text.o jsonpull/jsonpull.o dirtiles.o pmtiles_file.o
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3
|
||||
|
||||
tile-join: tile-join.o projection.o mbtiles.o mvt.o memfile.o dirtiles.o jsonpull/jsonpull.o text.o evaluator.o csv.o write_json.o pmtiles_file.o clip.o attribute.o thread.o
|
||||
tile-join: tile-join.o projection.o mbtiles.o mvt.o memfile.o dirtiles.o jsonpull/jsonpull.o text.o evaluator.o csv.o write_json.o pmtiles_file.o clip.o attribute.o thread.o read_json.o clipper2/src/clipper.engine.o
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
|
||||
|
||||
tippecanoe-json-tool: jsontool.o jsonpull/jsonpull.o csv.o text.o geojson-loop.o
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
|
||||
|
||||
unit: unit.o text.o sort.o mvt.o projection.o clip.o attribute.o jsonpull/jsonpull.o evaluator.o
|
||||
unit: unit.o text.o sort.o mvt.o projection.o clip.o attribute.o jsonpull/jsonpull.o evaluator.o read_json.o clipper2/src/clipper.engine.o
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
|
||||
|
||||
tippecanoe-overzoom: overzoom.o mvt.o clip.o evaluator.o jsonpull/jsonpull.o text.o attribute.o read_json.o projection.o
|
||||
tippecanoe-overzoom: overzoom.o mvt.o clip.o evaluator.o jsonpull/jsonpull.o text.o attribute.o read_json.o projection.o read_json.o clipper2/src/clipper.engine.o
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
|
||||
|
||||
-include $(wildcard *.d)
|
||||
@@ -291,8 +291,8 @@ overzoom-test: tippecanoe-overzoom
|
||||
./tippecanoe-decode tests/pbf/13-1310-3166.pbf 13 1310 3166 > tests/pbf/13-1310-3166.pbf.json.check
|
||||
cmp tests/pbf/13-1310-3166.pbf.json.check tests/pbf/13-1310-3166.pbf.json
|
||||
rm tests/pbf/13-1310-3166.pbf tests/pbf/13-1310-3166.pbf.json.check
|
||||
# Multiple inputs
|
||||
./tippecanoe-overzoom -o tests/pbf/13-1310-3166-ne.pbf -t 13/1310/3166 tests/pbf/11-327-791.pbf 11/327/791 tests/pbf/0-0-0.pbf 0/0/0
|
||||
# Multiple inputs, no compression
|
||||
./tippecanoe-overzoom --no-tile-compression -o tests/pbf/13-1310-3166-ne.pbf -t 13/1310/3166 tests/pbf/11-327-791.pbf 11/327/791 tests/pbf/0-0-0.pbf 0/0/0
|
||||
./tippecanoe-decode tests/pbf/13-1310-3166-ne.pbf 13 1310 3166 > tests/pbf/13-1310-3166-ne.pbf.json.check
|
||||
cmp tests/pbf/13-1310-3166-ne.pbf.json.check tests/pbf/13-1310-3166-ne.pbf.json
|
||||
rm tests/pbf/13-1310-3166-ne.pbf tests/pbf/13-1310-3166-ne.pbf.json.check
|
||||
@@ -362,16 +362,32 @@ overzoom-test: tippecanoe-overzoom
|
||||
./tippecanoe-decode tests/pbf/countries-0-0-0.pbf.out 0 0 0 > tests/pbf/countries-0-0-0.pbf.out.json.check
|
||||
cmp tests/pbf/countries-0-0-0.pbf.out.json.check tests/pbf/countries-0-0-0.pbf.out.json
|
||||
rm tests/pbf/countries-0-0-0.pbf.out tests/pbf/countries-0-0-0.pbf.out.json.check
|
||||
# Clipping to bounding box
|
||||
./tippecanoe-overzoom --clip-bounding-box 5,5,25.7,50 -o tests/pbf/countries-0-0-0-clip.pbf.out tests/pbf/countries-0-0-0.pbf 0/0/0 0/0/0
|
||||
./tippecanoe-decode tests/pbf/countries-0-0-0-clip.pbf.out 0 0 0 > tests/pbf/countries-0-0-0-clip.pbf.out.json.check
|
||||
cmp tests/pbf/countries-0-0-0-clip.pbf.out.json.check tests/pbf/countries-0-0-0-clip.pbf.out.json
|
||||
rm tests/pbf/countries-0-0-0-clip.pbf.out tests/pbf/countries-0-0-0-clip.pbf.out.json.check
|
||||
# Binning
|
||||
./tippecanoe-overzoom -o tests/pbf/bin-11-327-791.pbf.out --assign-to-bins tests/pbf/sf-zips.json tests/pbf/muni-11-327-791.pbf 11/327/791 11/327/791
|
||||
./tippecanoe-decode tests/pbf/bin-11-327-791.pbf.out 11 327 791 > tests/pbf/bin-11-327-791.pbf.out.json.check
|
||||
cmp tests/pbf/bin-11-327-791.pbf.out.json.check tests/pbf/bin-11-327-791.pbf.out.json
|
||||
rm tests/pbf/bin-11-327-791.pbf.out.json.check tests/pbf/bin-11-327-791.pbf.out
|
||||
# Binning by id
|
||||
./tippecanoe-overzoom -o tests/pbf/bin-11-327-791-ids.pbf.out --assign-to-bins tests/pbf/sf-zips.json --bin-by-id bin-ids tests/pbf/yearbuilt.pbf 11/327/791 11/327/791
|
||||
./tippecanoe-overzoom -o tests/pbf/bin-11-327-791-ids.pbf.out --assign-to-bins tests/pbf/sf-zips.json --bin-by-id-list bin-ids tests/pbf/yearbuilt.pbf 11/327/791 11/327/791
|
||||
./tippecanoe-decode tests/pbf/bin-11-327-791-ids.pbf.out 11 327 791 > tests/pbf/bin-11-327-791-ids.pbf.out.json.check
|
||||
cmp tests/pbf/bin-11-327-791-ids.pbf.out.json.check tests/pbf/bin-11-327-791-ids.pbf.out.json
|
||||
rm tests/pbf/bin-11-327-791-ids.pbf.out.json.check tests/pbf/bin-11-327-791-ids.pbf.out
|
||||
# Binning by id, clipping by polygon
|
||||
./tippecanoe-overzoom -o tests/pbf/bin-11-327-791-ids-clip.pbf.out --clip-polygon='{"coordinates":[[[-122.4527379,37.8128815],[-122.4598853,37.7834743],[-122.4280914,37.7959397],[-122.4527379,37.8128815]]],"type":"Polygon"}' --assign-to-bins tests/pbf/sf-zips.json --bin-by-id-list bin-ids tests/pbf/yearbuilt.pbf 11/327/791 11/327/791
|
||||
./tippecanoe-decode tests/pbf/bin-11-327-791-ids-clip.pbf.out 11 327 791 > tests/pbf/bin-11-327-791-ids-clip.pbf.out.json.check
|
||||
cmp tests/pbf/bin-11-327-791-ids-clip.pbf.out.json.check tests/pbf/bin-11-327-791-ids-clip.pbf.out.json
|
||||
rm tests/pbf/bin-11-327-791-ids-clip.pbf.out.json.check tests/pbf/bin-11-327-791-ids-clip.pbf.out
|
||||
# Binning by id, attribute stripping
|
||||
# Note that it still works even if we exclude the ID that we are binning by
|
||||
./tippecanoe-overzoom -yZCTA5CE10 -ytippecanoe:count -o tests/pbf/bin-11-327-791-ids-zip.pbf.out --assign-to-bins tests/pbf/sf-zips.json --bin-by-id-list bin-ids tests/pbf/yearbuilt.pbf 11/327/791 11/327/791
|
||||
./tippecanoe-decode tests/pbf/bin-11-327-791-ids-zip.pbf.out 11 327 791 > tests/pbf/bin-11-327-791-ids-zip.pbf.out.json.check
|
||||
cmp tests/pbf/bin-11-327-791-ids-zip.pbf.out.json.check tests/pbf/bin-11-327-791-ids-zip.pbf.out.json
|
||||
rm tests/pbf/bin-11-327-791-ids-zip.pbf.out.json.check tests/pbf/bin-11-327-791-ids-zip.pbf.out
|
||||
# Binning with longitude wraparound problems
|
||||
./tippecanoe-overzoom -o tests/pbf/0-0-0-pop-2-0-1.pbf.out --accumulate-numeric-attributes=tippecanoe --assign-to-bins tests/pbf/h3-2-0-1.geojson tests/pbf/0-0-0.pbf 2/0/1 2/0/1
|
||||
./tippecanoe-decode tests/pbf/0-0-0-pop-2-0-1.pbf.out 2 0 1 > tests/pbf/0-0-0-pop-2-0-1.pbf.out.json.check
|
||||
@@ -385,6 +401,34 @@ overzoom-test: tippecanoe-overzoom
|
||||
./tippecanoe-decode tests/pbf/0-0-0-pop-0-0-0.pbf.out 0 0 0 > tests/pbf/0-0-0-pop-0-0-0.pbf.out.json.check
|
||||
cmp tests/pbf/0-0-0-pop-0-0-0.pbf.out.json.check tests/pbf/0-0-0-pop-0-0-0.pbf.out.json
|
||||
rm tests/pbf/0-0-0-pop-0-0-0.pbf.out tests/pbf/0-0-0-pop-0-0-0.pbf.out.json.check
|
||||
# Binning, clipping to bounding box
|
||||
./tippecanoe-overzoom --clip-bounding-box 88,67.5,138,78 -o tests/pbf/0-0-0-pop-1-1-0-clip.pbf.out --accumulate-numeric-attributes=tippecanoe --assign-to-bins tests/pbf/h3-1-1-0.geojson tests/pbf/0-0-0.pbf 1/1/0 1/1/0
|
||||
./tippecanoe-decode tests/pbf/0-0-0-pop-1-1-0-clip.pbf.out 1 1 0 > tests/pbf/0-0-0-pop-1-1-0-clip.pbf.out.json.check
|
||||
cmp tests/pbf/0-0-0-pop-1-1-0-clip.pbf.out.json.check tests/pbf/0-0-0-pop-1-1-0-clip.pbf.out.json
|
||||
rm tests/pbf/0-0-0-pop-1-1-0-clip.pbf.out tests/pbf/0-0-0-pop-1-1-0-clip.pbf.out.json.check
|
||||
# Verify fix for crash
|
||||
./tippecanoe-overzoom '-o' tests/10188-crash/out.pbf '-t' '3/2/2' '--assign-to-bins' 'tests/10188-crash/bins.json' '--bin-by-id-list' 'felt:bin_features' '-b5' 'tests/10188-crash/2-0-0.pbf' '2/0/0'
|
||||
rm tests/10188-crash/out.pbf
|
||||
# Polygon clipping
|
||||
./tippecanoe-overzoom -o tests/pbf/countries-1-1-0-clip.pbf --clip-polygon "`cat tests/pbf/region.json`" tests/pbf/countries-1-1-0.pbf 1/1/0 1/1/0
|
||||
./tippecanoe-decode tests/pbf/countries-1-1-0-clip.pbf 1 1 0 > tests/pbf/countries-1-1-0-clip.json.check
|
||||
cmp tests/pbf/countries-1-1-0-clip.json.check tests/pbf/countries-1-1-0-clip.json
|
||||
rm tests/pbf/countries-1-1-0-clip.pbf tests/pbf/countries-1-1-0-clip.json.check
|
||||
# LineString clipping
|
||||
./tippecanoe-overzoom -o tests/pbf/roads-1-1-0-clip.pbf --clip-polygon "`cat tests/pbf/region.json`" tests/pbf/roads-1-1-0.pbf 1/1/0 1/1/0
|
||||
./tippecanoe-decode tests/pbf/roads-1-1-0-clip.pbf 1 1 0 > tests/pbf/roads-1-1-0-clip.json.check
|
||||
cmp tests/pbf/roads-1-1-0-clip.json.check tests/pbf/roads-1-1-0-clip.json
|
||||
rm tests/pbf/roads-1-1-0-clip.pbf tests/pbf/roads-1-1-0-clip.json.check
|
||||
# Point clipping
|
||||
./tippecanoe-overzoom -o tests/pbf/places-1-1-0-clip.pbf --clip-polygon "`cat tests/pbf/region.json`" tests/pbf/places-1-1-0.pbf 1/1/0 1/1/0
|
||||
./tippecanoe-decode tests/pbf/places-1-1-0-clip.pbf 1 1 0 > tests/pbf/places-1-1-0-clip.json.check
|
||||
cmp tests/pbf/places-1-1-0-clip.json.check tests/pbf/places-1-1-0-clip.json
|
||||
rm tests/pbf/places-1-1-0-clip.pbf tests/pbf/places-1-1-0-clip.json.check
|
||||
# Polygon clipping, with excessively large clip region
|
||||
./tippecanoe-overzoom -b10 -o tests/pbf/countries-8-135-86-bigclip.pbf --clip-polygon "`cat tests/pbf/region.json`" tests/pbf/countries-1-1-0.pbf 1/1/0 8/135/86
|
||||
./tippecanoe-decode tests/pbf/countries-8-135-86-bigclip.pbf 8 135 86 > tests/pbf/countries-8-135-86-bigclip.json.check
|
||||
cmp tests/pbf/countries-8-135-86-bigclip.json.check tests/pbf/countries-8-135-86-bigclip.json
|
||||
rm tests/pbf/countries-8-135-86-bigclip.pbf tests/pbf/countries-8-135-86-bigclip.json.check
|
||||
|
||||
join-test: tippecanoe tippecanoe-decode tile-join
|
||||
./tippecanoe -q -f -z12 -o tests/join-population/tabblock_06001420.mbtiles -YALAND10:'Land area' -L'{"file": "tests/join-population/tabblock_06001420.json", "description": "population"}'
|
||||
@@ -615,11 +659,36 @@ accumulate-test:
|
||||
# the cluster sizes still add up to the 243 original features
|
||||
test `./tippecanoe-decode -c tests/pbf/bins-0-0-0.pbf 0 0 0 | sed 's/.*clustered:cluster_size": //' | awk '{sum += $$1} END {print sum}'` == 243
|
||||
#
|
||||
# Binning with attribute stripping
|
||||
./tippecanoe-overzoom -y clustered:count:POP1950 -y clustered:sum:POP1950 -y POP1950 -y clustered:cluster_size --assign-to-bins tests/pbf/h3-0-0-0.geojson --accumulate-numeric-attributes=clustered --accumulate-attribute '{"clustered:cluster_size":"sum"}' -o tests/pbf/bins-0-0-0.pbf tests/pbf/accum.dir/0/0/0.pbf 0/0/0 0/0/0
|
||||
# Now there are 30 bins with POP1950 clusters
|
||||
test `./tippecanoe-decode -c tests/pbf/bins-0-0-0.pbf 0 0 0 | grep 'clustered:count:POP1950' | wc -l` == 41
|
||||
# There are none with bare POP1950 (which is expected; we should only have summary statistics)
|
||||
test `./tippecanoe-decode -c tests/pbf/bins-0-0-0.pbf 0 0 0 | grep -v 'clustered:count:POP1950' | grep 'POP1950' | wc -l` == 0
|
||||
# And 4 with no POP1950 at all
|
||||
test `./tippecanoe-decode -c tests/pbf/bins-0-0-0.pbf 0 0 0 | grep -v 'POP1950' | wc -l` == 3
|
||||
#
|
||||
# the clustered and megatile-filtered and binned POP1950s add up to 161590
|
||||
test `./tippecanoe-decode -c tests/pbf/bins-0-0-0.pbf 0 0 0 | grep 'clustered:sum:POP1950' | sed 's/.*"clustered:sum:POP1950": //' | awk '{sum += $$1} END {print sum}'` == 161590
|
||||
# which is the right global total
|
||||
# Make sure we do *not* accumulate a numeric attribute that already has the magic prefix:
|
||||
test `./tippecanoe-decode -c tests/pbf/bins-0-0-0.pbf 0 0 0 | grep sum:clustered:unrelated | wc -l` == 0
|
||||
# And those attributes do *not* make it onto the bins
|
||||
test `./tippecanoe-decode -c tests/pbf/bins-0-0-0.pbf 0 0 0 | grep clustered:unrelated | wc -l` == 0
|
||||
# the cluster sizes still add up to the 243 original features
|
||||
test `./tippecanoe-decode -c tests/pbf/bins-0-0-0.pbf 0 0 0 | sed 's/.*clustered:cluster_size": //' | awk '{sum += $$1} END {print sum}'` == 243
|
||||
#
|
||||
#
|
||||
# A tile where the counts and means were previously wrong:
|
||||
./tippecanoe-overzoom --accumulate-numeric-attributes=felt -m -o tests/pbf/yearbuilt-accum.pbf tests/pbf/yearbuilt.pbf 0/0/0 0/0/0
|
||||
./tippecanoe-decode tests/pbf/yearbuilt-accum.pbf 0 0 0 > tests/pbf/yearbuilt-accum.pbf.json.check
|
||||
cmp tests/pbf/yearbuilt-accum.pbf.json.check tests/pbf/yearbuilt-accum.pbf.json
|
||||
rm tests/pbf/yearbuilt-accum.pbf tests/pbf/yearbuilt-accum.pbf.json.check
|
||||
# Same tile, with attribute stripping
|
||||
./tippecanoe-overzoom --accumulate-numeric-attributes=felt -y bldgsqft -y felt:sum:bldgsqft -m -o tests/pbf/yearbuilt-accum-bldgsqft.pbf tests/pbf/yearbuilt.pbf 0/0/0 0/0/0
|
||||
./tippecanoe-decode tests/pbf/yearbuilt-accum-bldgsqft.pbf 0 0 0 > tests/pbf/yearbuilt-accum-bldgsqft.pbf.json.check
|
||||
cmp tests/pbf/yearbuilt-accum-bldgsqft.pbf.json.check tests/pbf/yearbuilt-accum-bldgsqft.pbf.json
|
||||
rm tests/pbf/yearbuilt-accum-bldgsqft.pbf tests/pbf/yearbuilt-accum-bldgsqft.pbf.json.check
|
||||
|
||||
join-filter-test: tippecanoe tippecanoe-decode tile-join
|
||||
# Comes out different from the direct tippecanoe run because null attributes are lost
|
||||
|
||||
+32
-35
@@ -88,36 +88,33 @@ void set_attribute_accum(std::unordered_map<std::string, attribute_op> &attribut
|
||||
set_attribute_accum(attribute_accum, name, type);
|
||||
}
|
||||
|
||||
void preserve_attribute(attribute_op const &op, std::string const &key, serial_val const &val, std::vector<std::string> &full_keys, std::vector<serial_val> &full_values, std::unordered_map<std::string, accum_state> &attribute_accum_state) {
|
||||
template <class T>
|
||||
static void preserve_attribute1(attribute_op const &op, std::string const &key, T const &val, std::vector<std::shared_ptr<std::string>> &full_keys, std::vector<T> &full_values, std::unordered_map<std::string, accum_state> &attribute_accum_state, key_pool &key_pool) {
|
||||
for (size_t i = 0; i < full_keys.size(); i++) {
|
||||
if (key == full_keys[i]) {
|
||||
if (key == *full_keys[i]) {
|
||||
switch (op) {
|
||||
case op_sum:
|
||||
full_values[i].s = milo::dtoa_milo(atof(full_values[i].s.c_str()) + atof(val.s.c_str()));
|
||||
full_values[i].type = mvt_double;
|
||||
full_values[i] = (full_values[i].to_double() + val.to_double());
|
||||
return;
|
||||
|
||||
case op_product:
|
||||
full_values[i].s = milo::dtoa_milo(atof(full_values[i].s.c_str()) * atof(val.s.c_str()));
|
||||
full_values[i].type = mvt_double;
|
||||
full_values[i] = (full_values[i].to_double() * val.to_double());
|
||||
return;
|
||||
|
||||
case op_max: {
|
||||
double existing = atof(full_values[i].s.c_str());
|
||||
double maybe = atof(val.s.c_str());
|
||||
double existing = full_values[i].to_double();
|
||||
double maybe = val.to_double();
|
||||
if (maybe > existing) {
|
||||
full_values[i].s = val.s.c_str();
|
||||
full_values[i].type = mvt_double;
|
||||
full_values[i] = val;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case op_min: {
|
||||
double existing = atof(full_values[i].s.c_str());
|
||||
double maybe = atof(val.s.c_str());
|
||||
double existing = full_values[i].to_double();
|
||||
double maybe = val.to_double();
|
||||
if (maybe < existing) {
|
||||
full_values[i].s = val.s.c_str();
|
||||
full_values[i].type = mvt_double;
|
||||
full_values[i] = val;
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -126,30 +123,26 @@ void preserve_attribute(attribute_op const &op, std::string const &key, serial_v
|
||||
auto state = attribute_accum_state.find(key);
|
||||
if (state == attribute_accum_state.end()) {
|
||||
accum_state s;
|
||||
s.sum = atof(full_values[i].s.c_str()) + atof(val.s.c_str());
|
||||
s.sum = full_values[i].to_double() + val.to_double();
|
||||
s.count = 2;
|
||||
attribute_accum_state.insert(std::pair<std::string, accum_state>(key, s));
|
||||
|
||||
full_values[i].s = milo::dtoa_milo(s.sum / s.count);
|
||||
full_values[i].type = mvt_double;
|
||||
full_values[i] = (s.sum / s.count);
|
||||
} else {
|
||||
state->second.sum += atof(val.s.c_str());
|
||||
state->second.sum += val.to_double();
|
||||
state->second.count += 1;
|
||||
|
||||
full_values[i].s = milo::dtoa_milo(state->second.sum / state->second.count);
|
||||
full_values[i].type = mvt_double;
|
||||
full_values[i] = (state->second.sum / state->second.count);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
case op_concat:
|
||||
full_values[i].s += val.s;
|
||||
full_values[i].type = mvt_string;
|
||||
full_values[i].set_string_value(full_values[i].get_string_value() + val.get_string_value());
|
||||
return;
|
||||
|
||||
case op_comma:
|
||||
full_values[i].s += std::string(",") + val.s;
|
||||
full_values[i].type = mvt_string;
|
||||
full_values[i].set_string_value(full_values[i].get_string_value() + "," + val.get_string_value());
|
||||
return;
|
||||
|
||||
case op_count: {
|
||||
@@ -159,12 +152,10 @@ void preserve_attribute(attribute_op const &op, std::string const &key, serial_v
|
||||
s.count = 2;
|
||||
attribute_accum_state.insert(std::pair<std::string, accum_state>(key, s));
|
||||
|
||||
full_values[i].type = mvt_double;
|
||||
full_values[i].s = std::to_string(s.count);
|
||||
full_values[i] = (s.count);
|
||||
} else { // already present, incrementing
|
||||
state->second.count += 1;
|
||||
full_values[i].type = mvt_double;
|
||||
full_values[i].s = std::to_string(state->second.count);
|
||||
full_values[i] = (state->second.count);
|
||||
}
|
||||
return;
|
||||
}
|
||||
@@ -174,13 +165,12 @@ void preserve_attribute(attribute_op const &op, std::string const &key, serial_v
|
||||
|
||||
// not found, so we are making a new value
|
||||
|
||||
serial_val sv;
|
||||
T v;
|
||||
switch (op) {
|
||||
case op_sum:
|
||||
case op_max:
|
||||
case op_min:
|
||||
sv.s = val.s;
|
||||
sv.type = mvt_double;
|
||||
v = val;
|
||||
break;
|
||||
|
||||
case op_count: {
|
||||
@@ -190,12 +180,11 @@ void preserve_attribute(attribute_op const &op, std::string const &key, serial_v
|
||||
s.count = 1;
|
||||
attribute_accum_state.insert(std::pair<std::string, accum_state>(key, s));
|
||||
|
||||
sv.s = std::to_string(s.count);
|
||||
v = (s.count);
|
||||
} else { // already present, incrementing
|
||||
fprintf(stderr, "preserve_attribute: can't happen (count)\n");
|
||||
exit(EXIT_IMPOSSIBLE);
|
||||
}
|
||||
sv.type = mvt_double;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -204,6 +193,14 @@ void preserve_attribute(attribute_op const &op, std::string const &key, serial_v
|
||||
exit(EXIT_IMPOSSIBLE);
|
||||
}
|
||||
|
||||
full_keys.push_back(key);
|
||||
full_values.push_back(sv);
|
||||
full_keys.push_back(key_pool.pool(key));
|
||||
full_values.push_back(v);
|
||||
}
|
||||
|
||||
void preserve_attribute(attribute_op const &op, std::string const &key, mvt_value const &val, std::vector<std::shared_ptr<std::string>> &full_keys, std::vector<mvt_value> &full_values, std::unordered_map<std::string, accum_state> &attribute_accum_state, key_pool &key_pool) {
|
||||
preserve_attribute1(op, key, val, full_keys, full_values, attribute_accum_state, key_pool);
|
||||
}
|
||||
|
||||
void preserve_attribute(attribute_op const &op, std::string const &key, serial_val const &val, std::vector<std::shared_ptr<std::string>> &full_keys, std::vector<serial_val> &full_values, std::unordered_map<std::string, accum_state> &attribute_accum_state, key_pool &key_pool) {
|
||||
preserve_attribute1(op, key, val, full_keys, full_values, attribute_accum_state, key_pool);
|
||||
}
|
||||
|
||||
+7
-1
@@ -4,6 +4,9 @@
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include "mvt.hpp"
|
||||
#include "milo/dtoa_milo.h"
|
||||
|
||||
enum attribute_op {
|
||||
op_sum,
|
||||
@@ -22,10 +25,13 @@ struct accum_state {
|
||||
};
|
||||
|
||||
struct serial_val;
|
||||
struct key_pool;
|
||||
|
||||
void set_attribute_accum(std::unordered_map<std::string, attribute_op> &attribute_accum, std::string name, std::string type);
|
||||
void set_attribute_accum(std::unordered_map<std::string, attribute_op> &attribute_accum, const char *arg, char **argv);
|
||||
void preserve_attribute(attribute_op const &op, const std::string &key, serial_val const &val, std::vector<std::string> &full_keys, std::vector<serial_val> &full_values, std::unordered_map<std::string, accum_state> &attribute_accum_state);
|
||||
|
||||
void preserve_attribute(attribute_op const &op, std::string const &key, serial_val const &val, std::vector<std::shared_ptr<std::string>> &full_keys, std::vector<serial_val> &full_values, std::unordered_map<std::string, accum_state> &attribute_accum_state, key_pool &key_pool);
|
||||
void preserve_attribute(attribute_op const &op, std::string const &key, mvt_value const &val, std::vector<std::shared_ptr<std::string>> &full_keys, std::vector<mvt_value> &full_values, std::unordered_map<std::string, accum_state> &attribute_accum_state, key_pool &key_pool);
|
||||
|
||||
extern std::map<std::string, attribute_op> numeric_operations;
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <mapbox/geometry/point.hpp>
|
||||
#include <mapbox/geometry/multi_polygon.hpp>
|
||||
#include <mapbox/geometry/wagyu/wagyu.hpp>
|
||||
#include <clipper2/clipper.h>
|
||||
#include <limits.h>
|
||||
#include "geometry.hpp"
|
||||
#include "errors.hpp"
|
||||
@@ -13,6 +14,7 @@
|
||||
#include "serial.hpp"
|
||||
#include "attribute.hpp"
|
||||
#include "projection.hpp"
|
||||
#include "read_json.hpp"
|
||||
|
||||
static std::vector<std::pair<double, double>> clip_poly1(std::vector<std::pair<double, double>> &geom,
|
||||
long long minx, long long miny, long long maxx, long long maxy,
|
||||
@@ -385,6 +387,123 @@ drawvec clean_or_clip_poly(drawvec &geom, int z, int buffer, bool clip, bool try
|
||||
return ret;
|
||||
}
|
||||
|
||||
drawvec clip_poly_poly(drawvec const &geom, drawvec const &bounds) {
|
||||
mapbox::geometry::multi_polygon<long long> result;
|
||||
|
||||
{
|
||||
mapbox::geometry::wagyu::wagyu<long long> wagyu;
|
||||
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
if (geom[i].op == VT_MOVETO) {
|
||||
mapbox::geometry::linear_ring<long long> lr;
|
||||
lr.push_back(mapbox::geometry::point<long long>(geom[i].x, geom[i].y));
|
||||
|
||||
size_t j;
|
||||
for (j = i + 1; j < geom.size(); j++) {
|
||||
if (geom[j].op != VT_LINETO) {
|
||||
break;
|
||||
}
|
||||
lr.push_back(mapbox::geometry::point<long long>(geom[j].x, geom[j].y));
|
||||
}
|
||||
|
||||
if (lr.size() >= 4) {
|
||||
wagyu.add_ring(lr);
|
||||
}
|
||||
|
||||
i = j - 1;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < bounds.size(); i++) {
|
||||
if (bounds[i].op == VT_MOVETO) {
|
||||
mapbox::geometry::linear_ring<long long> lr;
|
||||
lr.push_back(mapbox::geometry::point<long long>(bounds[i].x, bounds[i].y));
|
||||
|
||||
size_t j;
|
||||
for (j = i + 1; j < bounds.size(); j++) {
|
||||
if (bounds[j].op != VT_LINETO) {
|
||||
break;
|
||||
}
|
||||
lr.push_back(mapbox::geometry::point<long long>(bounds[j].x, bounds[j].y));
|
||||
}
|
||||
|
||||
if (lr.size() >= 4) {
|
||||
wagyu.add_ring(lr, mapbox::geometry::wagyu::polygon_type_clip);
|
||||
}
|
||||
|
||||
i = j - 1;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
result.clear();
|
||||
wagyu.execute(mapbox::geometry::wagyu::clip_type_intersection, result, mapbox::geometry::wagyu::fill_type_positive, mapbox::geometry::wagyu::fill_type_positive);
|
||||
} catch (std::runtime_error &e) {
|
||||
fprintf(stderr, "Internal error: Polygon clipping failed\n");
|
||||
exit(EXIT_IMPOSSIBLE);
|
||||
}
|
||||
}
|
||||
|
||||
drawvec ret;
|
||||
decode_clipped(result, ret, 1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
drawvec clip_point_poly(drawvec const &geom, drawvec const &bounds) {
|
||||
drawvec out;
|
||||
for (auto const &p : geom) {
|
||||
if (pnpoly_mp(bounds, p.x, p.y)) {
|
||||
out.push_back(p);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static Clipper2Lib::Paths64 geom_to_clipper(drawvec const &geom) {
|
||||
Clipper2Lib::Paths64 subject;
|
||||
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
if (geom[i].op == VT_MOVETO) {
|
||||
Clipper2Lib::Path64 path({{geom[i].x, geom[i].y}});
|
||||
size_t j;
|
||||
for (j = i + 1; j < geom.size(); j++) {
|
||||
if (geom[j].op != VT_LINETO) {
|
||||
break;
|
||||
}
|
||||
path.emplace_back(geom[j].x, geom[j].y);
|
||||
}
|
||||
subject.push_back(path);
|
||||
}
|
||||
}
|
||||
|
||||
return subject;
|
||||
}
|
||||
|
||||
static void clipper_to_geom(Clipper2Lib::Paths64 const &geom, drawvec &out) {
|
||||
for (auto const &ring : geom) {
|
||||
for (size_t i = 0; i < ring.size(); i++) {
|
||||
out.emplace_back(i == 0 ? VT_MOVETO : VT_LINETO, ring[i].x, ring[i].y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawvec clip_lines_poly(drawvec const &geom, drawvec const ®ion) {
|
||||
Clipper2Lib::Paths64 subject = geom_to_clipper(geom);
|
||||
Clipper2Lib::Paths64 clip = geom_to_clipper(region);
|
||||
|
||||
Clipper2Lib::Clipper64 clipper;
|
||||
clipper.AddOpenSubject(subject);
|
||||
clipper.AddClip(clip);
|
||||
|
||||
Clipper2Lib::Paths64 solution, open_solution;
|
||||
clipper.Execute(Clipper2Lib::ClipType::Intersection, Clipper2Lib::FillRule::Positive, solution, open_solution);
|
||||
|
||||
drawvec out;
|
||||
clipper_to_geom(solution, out);
|
||||
clipper_to_geom(open_solution, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
void to_tile_scale(drawvec &geom, int z, int detail) {
|
||||
if (32 - detail - z < 0) {
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
@@ -1075,8 +1194,70 @@ bool pnpoly_mp(std::vector<mvt_geometry> const &geom, long long x, long long y)
|
||||
return found;
|
||||
}
|
||||
|
||||
bool pnpoly_mp(drawvec const &geom, long long x, long long y) {
|
||||
// assumes rings are properly nested, so inside a hole matches twice
|
||||
bool found = false;
|
||||
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
if (geom[i].op == VT_MOVETO) {
|
||||
size_t j;
|
||||
for (j = i + 1; j < geom.size(); j++) {
|
||||
if (geom[j].op != VT_LINETO) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
found ^= pnpoly(geom, i, j - i, x, y);
|
||||
i = j - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return found;
|
||||
}
|
||||
|
||||
clipbbox parse_clip_poly(std::string arg) {
|
||||
json_pull *jp = json_begin_string(arg.c_str());
|
||||
json_object *j = json_read_tree(jp);
|
||||
if (j == NULL) {
|
||||
fprintf(stderr, "Expected JSON object, not %s\n", arg.c_str());
|
||||
exit(EXIT_ARGS);
|
||||
}
|
||||
if (j->type != JSON_HASH) {
|
||||
fprintf(stderr, "Expected JSON geometry object, not %s\n", arg.c_str());
|
||||
exit(EXIT_ARGS);
|
||||
}
|
||||
|
||||
std::pair<int, drawvec> parsed_geometry = parse_geometry(j, jp, j, 0, 0, 0, 1LL << 32, false, false);
|
||||
json_end(jp);
|
||||
|
||||
clipbbox out;
|
||||
out.minx = LLONG_MAX;
|
||||
out.miny = LLONG_MAX;
|
||||
out.maxx = LLONG_MIN;
|
||||
out.maxy = LLONG_MIN;
|
||||
for (auto const &d : parsed_geometry.second) {
|
||||
if (d.op == VT_MOVETO || d.op == VT_LINETO) {
|
||||
if (d.x < out.minx) {
|
||||
out.minx = d.x;
|
||||
}
|
||||
if (d.y < out.miny) {
|
||||
out.miny = d.y;
|
||||
}
|
||||
if (d.x > out.maxx) {
|
||||
out.maxx = d.x;
|
||||
}
|
||||
if (d.y > out.maxy) {
|
||||
out.maxy = d.y;
|
||||
}
|
||||
}
|
||||
}
|
||||
out.dv = std::move(parsed_geometry.second);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
std::string overzoom(std::vector<input_tile> const &tiles, int nz, int nx, int ny,
|
||||
int detail, int buffer,
|
||||
int detail_or_unspecified, int buffer,
|
||||
std::set<std::string> const &keep,
|
||||
std::set<std::string> const &exclude,
|
||||
std::vector<std::string> const &exclude_prefix,
|
||||
@@ -1087,7 +1268,8 @@ std::string overzoom(std::vector<input_tile> const &tiles, int nz, int nx, int n
|
||||
std::vector<std::string> const &unidecode_data, double simplification,
|
||||
double tiny_polygon_size,
|
||||
std::vector<mvt_layer> const &bins, std::string const &bin_by_id_list,
|
||||
std::string const &accumulate_numeric) {
|
||||
std::string const &accumulate_numeric, size_t feature_limit,
|
||||
std::vector<clipbbox> const &clipbboxes) {
|
||||
std::vector<source_tile> decoded;
|
||||
|
||||
for (auto const &t : tiles) {
|
||||
@@ -1113,7 +1295,7 @@ std::string overzoom(std::vector<input_tile> const &tiles, int nz, int nx, int n
|
||||
decoded.push_back(out);
|
||||
}
|
||||
|
||||
return overzoom(decoded, nz, nx, ny, detail, buffer, keep, exclude, exclude_prefix, do_compress, next_overzoomed_tiles, demultiply, filter, preserve_input_order, attribute_accum, unidecode_data, simplification, tiny_polygon_size, bins, bin_by_id_list, accumulate_numeric);
|
||||
return overzoom(decoded, nz, nx, ny, detail_or_unspecified, buffer, keep, exclude, exclude_prefix, do_compress, next_overzoomed_tiles, demultiply, filter, preserve_input_order, attribute_accum, unidecode_data, simplification, tiny_polygon_size, bins, bin_by_id_list, accumulate_numeric, feature_limit, clipbboxes);
|
||||
}
|
||||
|
||||
// like a minimal serial_feature, but with mvt_feature-style attributes
|
||||
@@ -1127,7 +1309,30 @@ struct tile_feature {
|
||||
size_t seq = 0;
|
||||
};
|
||||
|
||||
static void add_mean(mvt_feature &feature, mvt_layer &layer, std::string const &accumulate_numeric) {
|
||||
static bool should_keep(std::string const &key,
|
||||
std::set<std::string> const &keep,
|
||||
std::set<std::string> const &exclude,
|
||||
std::vector<std::string> const &exclude_prefix) {
|
||||
if (keep.size() == 0 || keep.find(key) != keep.end()) {
|
||||
if (exclude.find(key) != exclude.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto const &prefix : exclude_prefix) {
|
||||
if (starts_with(key, prefix)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void add_mean(mvt_feature &feature, mvt_layer &layer, std::string const &accumulate_numeric,
|
||||
std::set<std::string> const &keep, std::set<std::string> const &exclude,
|
||||
std::vector<std::string> const &exclude_prefix) {
|
||||
std::string accumulate_numeric_colon = accumulate_numeric + ":";
|
||||
|
||||
std::unordered_map<std::string, size_t> attributes;
|
||||
@@ -1156,21 +1361,26 @@ static void add_mean(mvt_feature &feature, mvt_layer &layer, std::string const &
|
||||
mvt_value mean;
|
||||
mean.type = mvt_double;
|
||||
mean.numeric_value.double_value = mvt_value_to_double(sum) / count_val;
|
||||
layer.tag(feature, accumulate_numeric + ":mean:" + trunc, mean);
|
||||
|
||||
if (should_keep(key, keep, exclude, exclude_prefix)) {
|
||||
layer.tag(feature, accumulate_numeric + ":mean:" + trunc, mean);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// accumulate :sum:, :min:, :max:, and :count: versions of the specified attribute
|
||||
static void preserve_numeric(const std::string &key, const mvt_value &val, // numeric attribute being accumulated
|
||||
std::vector<std::string> &full_keys, // keys of feature being accumulated onto
|
||||
std::vector<serial_val> &full_values, // values of features being accumulated onto
|
||||
const std::string &accumulate_numeric, // prefix of accumulations
|
||||
std::set<std::string> &keys, // key presence in the source feature
|
||||
std::map<std::string, size_t> &numeric_out_field, // key index in the output feature
|
||||
std::unordered_map<std::string, accum_state> &attribute_accum_state // accumulation state for preserve_attribute()
|
||||
) {
|
||||
static void preserve_numeric(const std::string &key, const mvt_value &val, // numeric attribute being accumulated
|
||||
std::vector<std::shared_ptr<std::string>> &full_keys, // keys of feature being accumulated onto
|
||||
std::vector<mvt_value> &full_values, // values of features being accumulated onto
|
||||
const std::string &accumulate_numeric, // prefix of accumulations
|
||||
std::set<std::string> &keys, // key presence in the source feature
|
||||
std::map<std::string, size_t> &numeric_out_field, // key index in the output feature
|
||||
std::unordered_map<std::string, accum_state> &attribute_accum_state, // accumulation state for preserve_attribute()
|
||||
key_pool &key_pool,
|
||||
std::set<std::string> const &keep, std::set<std::string> const &exclude,
|
||||
std::vector<std::string> const &exclude_prefix) {
|
||||
// If this is a numeric attribute, but there is also a prefix:sum (etc.) for the
|
||||
// same attribute, we want to use that one instead of this one.
|
||||
|
||||
@@ -1203,6 +1413,10 @@ static void preserve_numeric(const std::string &key, const mvt_value &val, /
|
||||
// and then put it back on for the output field
|
||||
std::string prefixed = accumulate_numeric + ":" + op.first + ":" + outkey;
|
||||
|
||||
if (!should_keep(prefixed, keep, exclude, exclude_prefix)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Does it exist in the output feature already?
|
||||
|
||||
auto prefixed_attr = numeric_out_field.find(prefixed);
|
||||
@@ -1213,41 +1427,37 @@ static void preserve_numeric(const std::string &key, const mvt_value &val, /
|
||||
if (out_attr == numeric_out_field.end()) {
|
||||
// not present at all, so copy our value to the prefixed output
|
||||
numeric_out_field.emplace(prefixed, full_keys.size());
|
||||
full_keys.push_back(prefixed);
|
||||
full_keys.push_back(key_pool.pool(prefixed));
|
||||
|
||||
if (op.second == op_count) {
|
||||
if (starting_from_accumulation) {
|
||||
// copy our count
|
||||
full_values.push_back(mvt_value_to_serial_val(val));
|
||||
full_values.push_back(val);
|
||||
} else {
|
||||
// new count of 1
|
||||
serial_val sv;
|
||||
sv.type = mvt_double;
|
||||
sv.s = "1";
|
||||
full_values.push_back(sv);
|
||||
full_values.push_back(mvt_value(1));
|
||||
}
|
||||
} else {
|
||||
full_values.push_back(mvt_value_to_serial_val(val));
|
||||
full_values.push_back(val);
|
||||
}
|
||||
} else {
|
||||
// exists unprefixed, so copy it, and then accumulate on our value
|
||||
numeric_out_field.emplace(prefixed, full_keys.size());
|
||||
full_keys.push_back(prefixed);
|
||||
full_keys.push_back(key_pool.pool(prefixed));
|
||||
|
||||
if (op.second == op_count) {
|
||||
serial_val sv;
|
||||
sv.type = mvt_double;
|
||||
mvt_value v;
|
||||
if (starting_from_accumulation) {
|
||||
// sum our count onto the existing 1
|
||||
sv.s = std::to_string(1 + mvt_value_to_long_long(val));
|
||||
v = mvt_value(1 + mvt_value_to_long_long(val));
|
||||
} else {
|
||||
// sum our 1 onto the existing 1
|
||||
sv.s = "2";
|
||||
v = mvt_value(2);
|
||||
}
|
||||
full_values.push_back(sv);
|
||||
full_values.push_back(v);
|
||||
} else {
|
||||
full_values.push_back(full_values[out_attr->second]);
|
||||
preserve_attribute(op.second, prefixed, mvt_value_to_serial_val(val), full_keys, full_values, attribute_accum_state);
|
||||
preserve_attribute(op.second, prefixed, val, full_keys, full_values, attribute_accum_state, key_pool);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -1255,51 +1465,119 @@ static void preserve_numeric(const std::string &key, const mvt_value &val, /
|
||||
if (op.second == op_count) {
|
||||
if (starting_from_accumulation) {
|
||||
// sum our count onto the existing count
|
||||
full_values[prefixed_attr->second].s = std::to_string(atoll(full_values[prefixed_attr->second].s.c_str()) + mvt_value_to_long_long(val));
|
||||
full_values[prefixed_attr->second] = mvt_value(mvt_value_to_long_long(full_values[prefixed_attr->second]) + mvt_value_to_long_long(val));
|
||||
} else {
|
||||
full_values[prefixed_attr->second].s = std::to_string(atoll(full_values[prefixed_attr->second].s.c_str()) + 1);
|
||||
full_values[prefixed_attr->second] = mvt_value(mvt_value_to_long_long(full_values[prefixed_attr->second]) + 1);
|
||||
}
|
||||
} else {
|
||||
preserve_attribute(op.second, prefixed, mvt_value_to_serial_val(val), full_keys, full_values, attribute_accum_state);
|
||||
preserve_attribute(op.second, prefixed, val, full_keys, full_values, attribute_accum_state, key_pool);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool should_keep(std::string const &key,
|
||||
std::set<std::string> const &keep,
|
||||
std::set<std::string> const &exclude,
|
||||
std::vector<std::string> const &exclude_prefix) {
|
||||
if (keep.size() == 0 || keep.find(key) != keep.end()) {
|
||||
if (exclude.find(key) != exclude.end()) {
|
||||
return false;
|
||||
}
|
||||
static void handle_closepath_from_mvt(drawvec &geom) {
|
||||
// mvt geometries close polygons with a mvt_closepath operation
|
||||
// tippecanoe-internal geometries close polygons with a lineto to the initial point
|
||||
|
||||
for (auto const &prefix : exclude_prefix) {
|
||||
if (starts_with(key, prefix)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
size_t last_open = 0;
|
||||
|
||||
return true;
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
if (geom[i].op == mvt_closepath) {
|
||||
geom[i] = draw(mvt_lineto, geom[last_open].x, geom[last_open].y);
|
||||
} else if (geom[i].op == mvt_moveto) {
|
||||
last_open = i;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void feature_out(std::vector<tile_feature> const &features, mvt_layer &outlayer,
|
||||
// returns true if a feature was output; false if it was clipped away
|
||||
static bool feature_out(std::vector<tile_feature> const &features, mvt_layer &outlayer,
|
||||
std::set<std::string> const &keep,
|
||||
std::set<std::string> const &exclude,
|
||||
std::vector<std::string> const &exclude_prefix,
|
||||
std::unordered_map<std::string, attribute_op> const &attribute_accum,
|
||||
std::shared_ptr<std::string> const &tile_stringpool,
|
||||
std::string const &accumulate_numeric) {
|
||||
std::string const &accumulate_numeric,
|
||||
key_pool &key_pool, int buffer, bool include_nonaggregate,
|
||||
std::vector<clipbbox> const &clipbboxes, int nz, int nx, int ny) {
|
||||
// Add geometry to output feature
|
||||
|
||||
drawvec geom = features[0].geom;
|
||||
int t = features[0].t;
|
||||
|
||||
bool fix_polygons = false;
|
||||
if ((buffer >= 0 || clipbboxes.size() > 0) && t == VT_POLYGON) {
|
||||
fix_polygons = true;
|
||||
}
|
||||
|
||||
if (fix_polygons) {
|
||||
handle_closepath_from_mvt(geom);
|
||||
}
|
||||
|
||||
if (buffer >= 0) {
|
||||
if (t == VT_LINE) {
|
||||
geom = clip_lines(geom, 32 - outlayer.detail(), buffer);
|
||||
} else if (t == VT_POLYGON) {
|
||||
drawvec dv;
|
||||
geom = simple_clip_poly(geom, 32 - outlayer.detail(), buffer, dv, false);
|
||||
} else if (t == VT_POINT) {
|
||||
geom = clip_point(geom, 32 - outlayer.detail(), buffer);
|
||||
}
|
||||
|
||||
geom = remove_noop(geom, t, 0);
|
||||
}
|
||||
|
||||
if (clipbboxes.size() != 0) {
|
||||
// bounding box is in world coordinates at world scale
|
||||
// feature is in local coordinates at tile scale
|
||||
|
||||
long long dx = (long long) nx << (32 - nz);
|
||||
long long dy = (long long) ny << (32 - nz);
|
||||
double scale = (double) outlayer.extent / (1LL << (32 - nz));
|
||||
|
||||
for (auto const &c_world : clipbboxes) {
|
||||
clipbbox c = c_world;
|
||||
|
||||
c.minx = std::llround((c_world.minx - dx) * scale);
|
||||
c.miny = std::llround((c_world.miny - dy) * scale);
|
||||
c.maxx = std::llround((c_world.maxx - dx) * scale);
|
||||
c.maxy = std::llround((c_world.maxy - dy) * scale);
|
||||
|
||||
for (auto &p : c.dv) {
|
||||
p.x = std::llround((p.x - dx) * scale);
|
||||
p.y = std::llround((p.y - dy) * scale);
|
||||
}
|
||||
|
||||
if (t == VT_POLYGON) {
|
||||
geom = simple_clip_poly(geom, c.minx, c.miny, c.maxx, c.maxy, false);
|
||||
if (c.dv.size() > 0 && geom.size() > 0) {
|
||||
geom = clip_poly_poly(geom, c.dv);
|
||||
}
|
||||
} else if (t == VT_LINE) {
|
||||
geom = clip_lines(geom, c.minx, c.miny, c.maxx, c.maxy);
|
||||
if (c.dv.size() > 0 && geom.size() > 0) {
|
||||
geom = clip_lines_poly(geom, c.dv);
|
||||
}
|
||||
} else if (t == VT_POINT) {
|
||||
geom = clip_point(geom, c.minx, c.miny, c.maxx, c.maxy);
|
||||
if (c.dv.size() > 0 && geom.size() > 0) {
|
||||
geom = clip_point_poly(geom, c.dv);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
geom = remove_noop(geom, t, 0);
|
||||
}
|
||||
|
||||
if (fix_polygons) {
|
||||
geom = clean_or_clip_poly(geom, 0, 0, false, false);
|
||||
geom = close_poly(geom);
|
||||
}
|
||||
|
||||
mvt_feature outfeature;
|
||||
outfeature.type = features[0].t;
|
||||
for (auto const &g : features[0].geom) {
|
||||
for (auto const &g : geom) {
|
||||
outfeature.geometry.emplace_back(g.op, g.x, g.y);
|
||||
}
|
||||
|
||||
@@ -1315,31 +1593,31 @@ static void feature_out(std::vector<tile_feature> const &features, mvt_layer &ou
|
||||
|
||||
if (attribute_accum.size() > 0 || accumulate_numeric.size() > 0) {
|
||||
// convert the attributes of the output feature
|
||||
// from mvt_value to serial_val so they can have
|
||||
// from layer references to a vector so they can have
|
||||
// attributes from the other features of the
|
||||
// multiplier cluster accumulated onto them
|
||||
|
||||
std::unordered_map<std::string, accum_state> attribute_accum_state;
|
||||
std::vector<std::string> full_keys;
|
||||
std::vector<serial_val> full_values;
|
||||
std::vector<std::shared_ptr<std::string>> full_keys;
|
||||
std::vector<mvt_value> full_values;
|
||||
std::map<std::string, size_t> numeric_out_field;
|
||||
|
||||
for (size_t i = 0; i + 1 < features[0].tags.size(); i += 2) {
|
||||
const std::string &key = features[0].layer->keys[features[0].tags[i]];
|
||||
auto f = attribute_accum.find(key);
|
||||
if (f != attribute_accum.end()) {
|
||||
// this attribute has an accumulator, so convert it
|
||||
full_keys.push_back(features[0].layer->keys[features[0].tags[i]]);
|
||||
full_values.push_back(mvt_value_to_serial_val(features[0].layer->values[features[0].tags[i + 1]]));
|
||||
} else if (accumulate_numeric.size() > 0 && features[0].layer->values[features[0].tags[i + 1]].is_numeric()) {
|
||||
// convert numeric for accumulation
|
||||
numeric_out_field.emplace(key, full_keys.size());
|
||||
full_keys.push_back(key);
|
||||
full_values.push_back(mvt_value_to_serial_val(features[0].layer->values[features[0].tags[i + 1]]));
|
||||
} else {
|
||||
// otherwise just tag it directly onto the output feature
|
||||
if (should_keep(features[0].layer->keys[features[0].tags[i]], keep, exclude, exclude_prefix)) {
|
||||
outlayer.tag(outfeature, features[0].layer->keys[features[0].tags[i]], features[0].layer->values[features[0].tags[i + 1]]);
|
||||
auto const &f = features[0];
|
||||
for (size_t i = 0; i + 1 < f.tags.size(); i += 2) {
|
||||
const std::string &key = f.layer->keys[f.tags[i]];
|
||||
if (should_keep(key, keep, exclude, exclude_prefix)) {
|
||||
if (attribute_accum.find(key) != attribute_accum.end()) {
|
||||
// this attribute has an accumulator, so convert it
|
||||
full_keys.push_back(key_pool.pool(f.layer->keys[f.tags[i]]));
|
||||
full_values.push_back(f.layer->values[f.tags[i + 1]]);
|
||||
} else if (accumulate_numeric.size() > 0 && f.layer->values[f.tags[i + 1]].is_numeric()) {
|
||||
// convert numeric for accumulation
|
||||
numeric_out_field.emplace(key, full_keys.size());
|
||||
full_keys.push_back(key_pool.pool(key));
|
||||
full_values.push_back(f.layer->values[f.tags[i + 1]]);
|
||||
} else if (include_nonaggregate) {
|
||||
// otherwise just tag it directly onto the output feature
|
||||
outlayer.tag(outfeature, f.layer->keys[f.tags[i]], f.layer->values[f.tags[i + 1]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1353,22 +1631,26 @@ static void feature_out(std::vector<tile_feature> const &features, mvt_layer &ou
|
||||
|
||||
for (size_t j = 0; j + 1 < features[i].tags.size(); j += 2) {
|
||||
const std::string &key = features[i].layer->keys[features[i].tags[j]];
|
||||
keys.insert(key);
|
||||
if (should_keep(key, keep, exclude, exclude_prefix)) {
|
||||
keys.insert(key);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t j = 0; j + 1 < features[i].tags.size(); j += 2) {
|
||||
const std::string &key = features[i].layer->keys[features[i].tags[j]];
|
||||
|
||||
auto f = attribute_accum.find(key);
|
||||
if (f != attribute_accum.end()) {
|
||||
serial_val val = mvt_value_to_serial_val(features[i].layer->values[features[i].tags[j + 1]]);
|
||||
preserve_attribute(f->second, key, val, full_keys, full_values, attribute_accum_state);
|
||||
} else if (accumulate_numeric.size() > 0) {
|
||||
const mvt_value &val = features[i].layer->values[features[i].tags[j + 1]];
|
||||
if (val.is_numeric()) {
|
||||
preserve_numeric(key, val, full_keys, full_values,
|
||||
accumulate_numeric,
|
||||
keys, numeric_out_field, attribute_accum_state);
|
||||
if (should_keep(key, keep, exclude, exclude_prefix)) {
|
||||
auto found = attribute_accum.find(key);
|
||||
if (found != attribute_accum.end()) {
|
||||
mvt_value val = features[i].layer->values[features[i].tags[j + 1]];
|
||||
preserve_attribute(found->second, key, val, full_keys, full_values, attribute_accum_state, key_pool);
|
||||
} else if (accumulate_numeric.size() > 0) {
|
||||
const mvt_value &val = features[i].layer->values[features[i].tags[j + 1]];
|
||||
if (val.is_numeric()) {
|
||||
preserve_numeric(key, val, full_keys, full_values,
|
||||
accumulate_numeric,
|
||||
keys, numeric_out_field, attribute_accum_state, key_pool,
|
||||
keep, exclude, exclude_prefix);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1378,15 +1660,15 @@ static void feature_out(std::vector<tile_feature> const &features, mvt_layer &ou
|
||||
// and tag them onto the output feature
|
||||
|
||||
for (size_t i = 0; i < full_keys.size(); i++) {
|
||||
if (should_keep(full_keys[i], keep, exclude, exclude_prefix)) {
|
||||
outlayer.tag(outfeature, full_keys[i], stringified_to_mvt_value(full_values[i].type, full_values[i].s.c_str(), tile_stringpool));
|
||||
if (should_keep(*full_keys[i], keep, exclude, exclude_prefix)) {
|
||||
outlayer.tag(outfeature, *full_keys[i], full_values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
if (accumulate_numeric.size() > 0) {
|
||||
add_mean(outfeature, outlayer, accumulate_numeric);
|
||||
add_mean(outfeature, outlayer, accumulate_numeric, keep, exclude, exclude_prefix);
|
||||
}
|
||||
} else {
|
||||
} else if (include_nonaggregate) {
|
||||
for (size_t i = 0; i + 1 < features[0].tags.size(); i += 2) {
|
||||
if (should_keep(features[0].layer->keys[features[0].tags[i]], keep, exclude, exclude_prefix)) {
|
||||
outlayer.tag(outfeature, features[0].layer->keys[features[0].tags[i]], features[0].layer->values[features[0].tags[i + 1]]);
|
||||
@@ -1395,7 +1677,10 @@ static void feature_out(std::vector<tile_feature> const &features, mvt_layer &ou
|
||||
}
|
||||
|
||||
outlayer.features.push_back(std::move(outfeature));
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct preservecmp {
|
||||
@@ -1520,13 +1805,19 @@ static std::vector<size_t> parse_ids_string(mvt_value const &v) {
|
||||
|
||||
mvt_tile assign_to_bins(mvt_tile &features,
|
||||
std::vector<mvt_layer> const &bins, std::string const &bin_by_id_list,
|
||||
int z, int x, int y, int detail,
|
||||
int z, int x, int y,
|
||||
std::unordered_map<std::string, attribute_op> const &attribute_accum,
|
||||
std::string const &accumulate_numeric,
|
||||
std::set<std::string> keep,
|
||||
std::set<std::string> exclude,
|
||||
std::vector<std::string> exclude_prefix) {
|
||||
std::vector<std::string> exclude_prefix,
|
||||
int buffer, std::vector<clipbbox> const &clipbboxes) {
|
||||
std::vector<index_event> events;
|
||||
key_pool key_pool;
|
||||
|
||||
if (bins.size() == 0) {
|
||||
return mvt_tile();
|
||||
}
|
||||
|
||||
// Index bins
|
||||
for (size_t i = 0; i < bins.size(); i++) {
|
||||
@@ -1534,7 +1825,7 @@ mvt_tile assign_to_bins(mvt_tile &features,
|
||||
long long xmin, ymin, xmax, ymax;
|
||||
unsigned long long start, end;
|
||||
|
||||
get_bbox(bins[i].features[j].geometry, &xmin, &ymin, &xmax, &ymax, z, x, y, detail);
|
||||
get_bbox(bins[i].features[j].geometry, &xmin, &ymin, &xmax, &ymax, z, x, y, bins[i].detail());
|
||||
get_quadkey_bounds(xmin, ymin, xmax, ymax, &start, &end);
|
||||
events.emplace_back(start, index_event::ENTER, i, j, xmin, ymin, xmax, ymax);
|
||||
events.emplace_back(end, index_event::EXIT, i, j, xmin, ymin, xmax, ymax);
|
||||
@@ -1554,7 +1845,7 @@ mvt_tile assign_to_bins(mvt_tile &features,
|
||||
fid_to_feature.emplace(features.layers[i].features[j].id, std::make_pair(i, j));
|
||||
}
|
||||
|
||||
get_bbox(features.layers[i].features[j].geometry, &xmin, &ymin, &xmax, &ymax, z, x, y, detail);
|
||||
get_bbox(features.layers[i].features[j].geometry, &xmin, &ymin, &xmax, &ymax, z, x, y, features.layers[i].detail());
|
||||
get_quadkey_bounds(xmin, ymin, xmax, ymax, &start, &end);
|
||||
events.emplace_back(start, index_event::CHECK, i, j, xmin, ymin, xmax, ymax);
|
||||
}
|
||||
@@ -1565,12 +1856,11 @@ mvt_tile assign_to_bins(mvt_tile &features,
|
||||
std::set<active_bin> active;
|
||||
|
||||
mvt_layer outlayer;
|
||||
outlayer.extent = 1 << detail;
|
||||
outlayer.extent = bins[0].extent;
|
||||
outlayer.version = 2;
|
||||
outlayer.name = features.layers[0].name;
|
||||
|
||||
std::vector<std::vector<tile_feature>> outfeatures;
|
||||
std::shared_ptr<std::string> tile_stringpool = std::make_shared<std::string>();
|
||||
|
||||
for (auto &e : events) {
|
||||
if (e.kind == index_event::ENTER) {
|
||||
@@ -1629,6 +1919,10 @@ mvt_tile assign_to_bins(mvt_tile &features,
|
||||
|
||||
active.insert(std::move(a));
|
||||
} else if (e.kind == index_event::CHECK) {
|
||||
if (bin_by_id_list.size() > 0) {
|
||||
continue; // only bin by id, not geometrically
|
||||
}
|
||||
|
||||
auto const &feature = features.layers[e.layer].features[e.feature];
|
||||
|
||||
if (feature.geometry.size() == 0) {
|
||||
@@ -1678,21 +1972,25 @@ mvt_tile assign_to_bins(mvt_tile &features,
|
||||
|
||||
for (size_t i = 0; i < outfeatures.size(); i++) {
|
||||
if (outfeatures[i].size() > 1) {
|
||||
feature_out(outfeatures[i], outlayer,
|
||||
keep, exclude, exclude_prefix, attribute_accum,
|
||||
tile_stringpool, accumulate_numeric);
|
||||
mvt_feature &nfeature = outlayer.features.back();
|
||||
mvt_value val;
|
||||
val.type = mvt_uint;
|
||||
val.numeric_value.uint_value = outfeatures[i].size() - 1;
|
||||
if (feature_out(outfeatures[i], outlayer,
|
||||
keep, exclude, exclude_prefix, attribute_accum,
|
||||
accumulate_numeric, key_pool, buffer, true,
|
||||
clipbboxes, z, x, y)) {
|
||||
mvt_feature &nfeature = outlayer.features.back();
|
||||
mvt_value val;
|
||||
val.type = mvt_uint;
|
||||
val.numeric_value.uint_value = outfeatures[i].size() - 1;
|
||||
|
||||
std::string attrname;
|
||||
if (accumulate_numeric.size() == 0) {
|
||||
attrname = "tippecanoe:count";
|
||||
} else {
|
||||
attrname = accumulate_numeric + ":count";
|
||||
std::string attrname;
|
||||
if (accumulate_numeric.size() == 0) {
|
||||
attrname = "tippecanoe:count";
|
||||
} else {
|
||||
attrname = accumulate_numeric + ":count";
|
||||
}
|
||||
if (should_keep(attrname, keep, exclude, exclude_prefix)) {
|
||||
outlayer.tag(nfeature, attrname, val);
|
||||
}
|
||||
}
|
||||
outlayer.tag(nfeature, attrname, val);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1702,7 +2000,7 @@ mvt_tile assign_to_bins(mvt_tile &features,
|
||||
}
|
||||
|
||||
std::string overzoom(std::vector<source_tile> const &tiles, int nz, int nx, int ny,
|
||||
int detail, int buffer,
|
||||
int detail_or_unspecified, int buffer,
|
||||
std::set<std::string> const &keep,
|
||||
std::set<std::string> const &exclude,
|
||||
std::vector<std::string> const &exclude_prefix,
|
||||
@@ -1713,15 +2011,16 @@ std::string overzoom(std::vector<source_tile> const &tiles, int nz, int nx, int
|
||||
std::vector<std::string> const &unidecode_data, double simplification,
|
||||
double tiny_polygon_size,
|
||||
std::vector<mvt_layer> const &bins, std::string const &bin_by_id_list,
|
||||
std::string const &accumulate_numeric) {
|
||||
std::string const &accumulate_numeric, size_t feature_limit,
|
||||
std::vector<clipbbox> const &clipbboxes) {
|
||||
mvt_tile outtile;
|
||||
std::shared_ptr<std::string> tile_stringpool = std::make_shared<std::string>();
|
||||
key_pool key_pool;
|
||||
|
||||
for (auto const &tile : tiles) {
|
||||
for (auto const &layer : tile.tile.layers) {
|
||||
mvt_layer *outlayer = NULL;
|
||||
|
||||
int det = detail;
|
||||
int det = detail_or_unspecified;
|
||||
if (det <= 0) {
|
||||
det = std::round(log(layer.extent) / log(2));
|
||||
}
|
||||
@@ -1773,8 +2072,32 @@ std::string overzoom(std::vector<source_tile> const &tiles, int nz, int nx, int
|
||||
}
|
||||
}
|
||||
|
||||
// Clip to user-specified bounding boxes.
|
||||
// Bounding box clip first, to reduce complexity of the full clip.
|
||||
// But don't clip here if we are binning, because we need to bin points in the buffer
|
||||
if (bins.size() == 0) {
|
||||
for (auto &c : clipbboxes) {
|
||||
if (t == VT_POLYGON) {
|
||||
geom = simple_clip_poly(geom, c.minx, c.miny, c.maxx, c.maxy, false);
|
||||
if (c.dv.size() > 0 && geom.size() > 0) {
|
||||
geom = clip_poly_poly(geom, c.dv);
|
||||
}
|
||||
} else if (t == VT_LINE) {
|
||||
geom = clip_lines(geom, c.minx, c.miny, c.maxx, c.maxy);
|
||||
if (c.dv.size() > 0 && geom.size() > 0) {
|
||||
geom = clip_lines_poly(geom, c.dv);
|
||||
}
|
||||
} else if (t == VT_POINT) {
|
||||
geom = clip_point(geom, c.minx, c.miny, c.maxx, c.maxy);
|
||||
if (c.dv.size() > 0 && geom.size() > 0) {
|
||||
geom = clip_point_poly(geom, c.dv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now offset from world coordinates to output tile coordinates,
|
||||
// but retain world scale, because that is what tippecanoe clipping expects
|
||||
// but retain world scale, because that is what tippecanoe zoom-oriented clipping expects
|
||||
|
||||
long long outtilesize = 1LL << (32 - nz); // destination tile size in world coordinates
|
||||
for (auto &g : geom) {
|
||||
@@ -1840,7 +2163,10 @@ std::string overzoom(std::vector<source_tile> const &tiles, int nz, int nx, int
|
||||
|
||||
if (flush_multiplier_cluster) {
|
||||
if (pending_tile_features.size() > 0) {
|
||||
feature_out(pending_tile_features, *outlayer, keep, exclude, exclude_prefix, attribute_accum, tile_stringpool, accumulate_numeric);
|
||||
feature_out(pending_tile_features, *outlayer, keep, exclude, exclude_prefix, attribute_accum, accumulate_numeric, key_pool, -1, bins.size() == 0, std::vector<clipbbox>(), nz, nx, ny);
|
||||
if (outlayer->features.size() >= feature_limit) {
|
||||
break;
|
||||
}
|
||||
pending_tile_features.clear();
|
||||
}
|
||||
}
|
||||
@@ -1854,16 +2180,16 @@ std::string overzoom(std::vector<source_tile> const &tiles, int nz, int nx, int
|
||||
if (t == VT_POLYGON && tiny_polygon_size > 0) {
|
||||
bool simplified_away_by_reduction = false;
|
||||
|
||||
geom = reduce_tiny_poly(geom, nz, detail, &still_need_simplification_after_reduction, &simplified_away_by_reduction, &accum_area, tiny_polygon_size);
|
||||
geom = reduce_tiny_poly(geom, nz, det, &still_need_simplification_after_reduction, &simplified_away_by_reduction, &accum_area, tiny_polygon_size);
|
||||
} else {
|
||||
still_need_simplification_after_reduction = true;
|
||||
}
|
||||
|
||||
if (simplification > 0 && still_need_simplification_after_reduction) {
|
||||
if (t == VT_POLYGON) {
|
||||
geom = simplify_lines_basic(geom, nz, detail, simplification, 4);
|
||||
geom = simplify_lines_basic(geom, nz, det, simplification, 4);
|
||||
} else if (t == VT_LINE) {
|
||||
geom = simplify_lines_basic(geom, nz, detail, simplification, 0);
|
||||
geom = simplify_lines_basic(geom, nz, det, simplification, 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1897,8 +2223,11 @@ std::string overzoom(std::vector<source_tile> const &tiles, int nz, int nx, int
|
||||
}
|
||||
|
||||
if (pending_tile_features.size() > 0) {
|
||||
feature_out(pending_tile_features, *outlayer, keep, exclude, exclude_prefix, attribute_accum, tile_stringpool, accumulate_numeric);
|
||||
feature_out(pending_tile_features, *outlayer, keep, exclude, exclude_prefix, attribute_accum, accumulate_numeric, key_pool, -1, bins.size() == 0, std::vector<clipbbox>(), nz, nx, ny);
|
||||
pending_tile_features.clear();
|
||||
if (outlayer->features.size() >= feature_limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (preserve_input_order) {
|
||||
@@ -1927,10 +2256,14 @@ std::string overzoom(std::vector<source_tile> const &tiles, int nz, int nx, int
|
||||
std::vector<source_tile> sts;
|
||||
sts.push_back(st);
|
||||
|
||||
// feature_limit arg is 1, because we just care whether there are any overzoomed features
|
||||
// left after clipping to the child tile, not about their actual content
|
||||
std::string child = overzoom(sts,
|
||||
nz + 1, nx * 2 + x, ny * 2 + y,
|
||||
detail, buffer, keep, exclude, exclude_prefix, false, NULL,
|
||||
demultiply, filter, preserve_input_order, attribute_accum, unidecode_data, simplification, tiny_polygon_size, bins, bin_by_id_list, accumulate_numeric);
|
||||
detail_or_unspecified, buffer, keep, exclude, exclude_prefix, false, NULL,
|
||||
demultiply, filter, preserve_input_order, attribute_accum, unidecode_data,
|
||||
simplification, tiny_polygon_size, bins, bin_by_id_list, accumulate_numeric,
|
||||
1, clipbboxes);
|
||||
if (child.size() > 0) {
|
||||
next_overzoomed_tiles->emplace_back(nx * 2 + x, ny * 2 + y);
|
||||
}
|
||||
@@ -1940,8 +2273,9 @@ std::string overzoom(std::vector<source_tile> const &tiles, int nz, int nx, int
|
||||
}
|
||||
|
||||
if (bins.size() > 0) {
|
||||
outtile = assign_to_bins(outtile, bins, bin_by_id_list, nz, nx, ny, detail, attribute_accum, accumulate_numeric,
|
||||
keep, exclude, exclude_prefix);
|
||||
outtile = assign_to_bins(outtile, bins, bin_by_id_list, nz, nx, ny,
|
||||
attribute_accum, accumulate_numeric,
|
||||
keep, exclude, exclude_prefix, buffer, clipbboxes);
|
||||
}
|
||||
|
||||
for (ssize_t i = outtile.layers.size() - 1; i >= 0; i--) {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
Boost Software License - Version 1.0 - August 17th, 2003
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
this license (the "Software") to use, reproduce, display, distribute,
|
||||
execute, and transmit the Software, and to prepare derivative works of the
|
||||
Software, and to permit third-parties to whom the Software is furnished to
|
||||
do so, all subject to the following:
|
||||
|
||||
The copyright notices in the Software and this entire statement, including
|
||||
the above license grant, this restriction and the following disclaimer,
|
||||
must be included in all copies of the Software, in whole or in part, and
|
||||
all derivative works of the Software, unless such copies or derivative
|
||||
works are solely in the form of machine-executable object code generated by
|
||||
a source language processor.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
|
||||
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
|
||||
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,641 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 17 September 2024 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Purpose : This is the main polygon clipping module *
|
||||
* License : http://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_ENGINE_H
|
||||
#define CLIPPER_ENGINE_H
|
||||
|
||||
#include <cstdlib>
|
||||
#include <stdint.h> //#541
|
||||
#include <iostream>
|
||||
#include <queue>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <numeric>
|
||||
#include <memory>
|
||||
|
||||
#include "clipper2/clipper.core.h"
|
||||
|
||||
namespace Clipper2Lib {
|
||||
|
||||
struct Scanline;
|
||||
struct IntersectNode;
|
||||
struct Active;
|
||||
struct Vertex;
|
||||
struct LocalMinima;
|
||||
struct OutRec;
|
||||
struct HorzSegment;
|
||||
|
||||
//Note: all clipping operations except for Difference are commutative.
|
||||
enum class ClipType { NoClip, Intersection, Union, Difference, Xor };
|
||||
|
||||
enum class PathType { Subject, Clip };
|
||||
enum class JoinWith { NoJoin, Left, Right };
|
||||
|
||||
enum class VertexFlags : uint32_t {
|
||||
Empty = 0, OpenStart = 1, OpenEnd = 2, LocalMax = 4, LocalMin = 8
|
||||
};
|
||||
|
||||
constexpr enum VertexFlags operator &(enum VertexFlags a, enum VertexFlags b)
|
||||
{
|
||||
return (enum VertexFlags)(uint32_t(a) & uint32_t(b));
|
||||
}
|
||||
|
||||
constexpr enum VertexFlags operator |(enum VertexFlags a, enum VertexFlags b)
|
||||
{
|
||||
return (enum VertexFlags)(uint32_t(a) | uint32_t(b));
|
||||
}
|
||||
|
||||
struct Vertex {
|
||||
Point64 pt;
|
||||
Vertex* next = nullptr;
|
||||
Vertex* prev = nullptr;
|
||||
VertexFlags flags = VertexFlags::Empty;
|
||||
};
|
||||
|
||||
struct OutPt {
|
||||
Point64 pt;
|
||||
OutPt* next = nullptr;
|
||||
OutPt* prev = nullptr;
|
||||
OutRec* outrec;
|
||||
HorzSegment* horz = nullptr;
|
||||
|
||||
OutPt(const Point64& pt_, OutRec* outrec_): pt(pt_), outrec(outrec_) {
|
||||
next = this;
|
||||
prev = this;
|
||||
}
|
||||
};
|
||||
|
||||
class PolyPath;
|
||||
class PolyPath64;
|
||||
class PolyPathD;
|
||||
using PolyTree64 = PolyPath64;
|
||||
using PolyTreeD = PolyPathD;
|
||||
|
||||
struct OutRec;
|
||||
typedef std::vector<OutRec*> OutRecList;
|
||||
|
||||
//OutRec: contains a path in the clipping solution. Edges in the AEL will
|
||||
//have OutRec pointers assigned when they form part of the clipping solution.
|
||||
struct OutRec {
|
||||
size_t idx = 0;
|
||||
OutRec* owner = nullptr;
|
||||
Active* front_edge = nullptr;
|
||||
Active* back_edge = nullptr;
|
||||
OutPt* pts = nullptr;
|
||||
PolyPath* polypath = nullptr;
|
||||
OutRecList* splits = nullptr;
|
||||
OutRec* recursive_split = nullptr;
|
||||
Rect64 bounds = {};
|
||||
Path64 path;
|
||||
bool is_open = false;
|
||||
|
||||
~OutRec() {
|
||||
if (splits) delete splits;
|
||||
// nb: don't delete the split pointers
|
||||
// as these are owned by ClipperBase's outrec_list_
|
||||
};
|
||||
};
|
||||
|
||||
///////////////////////////////////////////////////////////////////
|
||||
//Important: UP and DOWN here are premised on Y-axis positive down
|
||||
//displays, which is the orientation used in Clipper's development.
|
||||
///////////////////////////////////////////////////////////////////
|
||||
|
||||
struct Active {
|
||||
Point64 bot;
|
||||
Point64 top;
|
||||
int64_t curr_x = 0; //current (updated at every new scanline)
|
||||
double dx = 0.0;
|
||||
int wind_dx = 1; //1 or -1 depending on winding direction
|
||||
int wind_cnt = 0;
|
||||
int wind_cnt2 = 0; //winding count of the opposite polytype
|
||||
OutRec* outrec = nullptr;
|
||||
//AEL: 'active edge list' (Vatti's AET - active edge table)
|
||||
// a linked list of all edges (from left to right) that are present
|
||||
// (or 'active') within the current scanbeam (a horizontal 'beam' that
|
||||
// sweeps from bottom to top over the paths in the clipping operation).
|
||||
Active* prev_in_ael = nullptr;
|
||||
Active* next_in_ael = nullptr;
|
||||
//SEL: 'sorted edge list' (Vatti's ST - sorted table)
|
||||
// linked list used when sorting edges into their new positions at the
|
||||
// top of scanbeams, but also (re)used to process horizontals.
|
||||
Active* prev_in_sel = nullptr;
|
||||
Active* next_in_sel = nullptr;
|
||||
Active* jump = nullptr;
|
||||
Vertex* vertex_top = nullptr;
|
||||
LocalMinima* local_min = nullptr; // the bottom of an edge 'bound' (also Vatti)
|
||||
bool is_left_bound = false;
|
||||
JoinWith join_with = JoinWith::NoJoin;
|
||||
};
|
||||
|
||||
struct LocalMinima {
|
||||
Vertex* vertex;
|
||||
PathType polytype;
|
||||
bool is_open;
|
||||
LocalMinima(Vertex* v, PathType pt, bool open) :
|
||||
vertex(v), polytype(pt), is_open(open){}
|
||||
};
|
||||
|
||||
struct IntersectNode {
|
||||
Point64 pt;
|
||||
Active* edge1;
|
||||
Active* edge2;
|
||||
IntersectNode() : pt(Point64(0,0)), edge1(NULL), edge2(NULL) {}
|
||||
IntersectNode(Active* e1, Active* e2, Point64& pt_) :
|
||||
pt(pt_), edge1(e1), edge2(e2) {}
|
||||
};
|
||||
|
||||
struct HorzSegment {
|
||||
OutPt* left_op;
|
||||
OutPt* right_op = nullptr;
|
||||
bool left_to_right = true;
|
||||
HorzSegment() : left_op(nullptr) { }
|
||||
explicit HorzSegment(OutPt* op) : left_op(op) { }
|
||||
};
|
||||
|
||||
struct HorzJoin {
|
||||
OutPt* op1 = nullptr;
|
||||
OutPt* op2 = nullptr;
|
||||
HorzJoin() {};
|
||||
explicit HorzJoin(OutPt* ltr, OutPt* rtl) : op1(ltr), op2(rtl) { }
|
||||
};
|
||||
|
||||
#ifdef USINGZ
|
||||
typedef std::function<void(const Point64& e1bot, const Point64& e1top,
|
||||
const Point64& e2bot, const Point64& e2top, Point64& pt)> ZCallback64;
|
||||
|
||||
typedef std::function<void(const PointD& e1bot, const PointD& e1top,
|
||||
const PointD& e2bot, const PointD& e2top, PointD& pt)> ZCallbackD;
|
||||
#endif
|
||||
|
||||
typedef std::vector<HorzSegment> HorzSegmentList;
|
||||
typedef std::unique_ptr<LocalMinima> LocalMinima_ptr;
|
||||
typedef std::vector<LocalMinima_ptr> LocalMinimaList;
|
||||
typedef std::vector<IntersectNode> IntersectNodeList;
|
||||
|
||||
// ReuseableDataContainer64 ------------------------------------------------
|
||||
|
||||
class ReuseableDataContainer64 {
|
||||
private:
|
||||
friend class ClipperBase;
|
||||
LocalMinimaList minima_list_;
|
||||
std::vector<Vertex*> vertex_lists_;
|
||||
void AddLocMin(Vertex& vert, PathType polytype, bool is_open);
|
||||
public:
|
||||
virtual ~ReuseableDataContainer64();
|
||||
void Clear();
|
||||
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
|
||||
};
|
||||
|
||||
// ClipperBase -------------------------------------------------------------
|
||||
|
||||
class ClipperBase {
|
||||
private:
|
||||
ClipType cliptype_ = ClipType::NoClip;
|
||||
FillRule fillrule_ = FillRule::EvenOdd;
|
||||
FillRule fillpos = FillRule::Positive;
|
||||
int64_t bot_y_ = 0;
|
||||
bool minima_list_sorted_ = false;
|
||||
bool using_polytree_ = false;
|
||||
Active* actives_ = nullptr;
|
||||
Active *sel_ = nullptr;
|
||||
LocalMinimaList minima_list_; //pointers in case of memory reallocs
|
||||
LocalMinimaList::iterator current_locmin_iter_;
|
||||
std::vector<Vertex*> vertex_lists_;
|
||||
std::priority_queue<int64_t> scanline_list_;
|
||||
IntersectNodeList intersect_nodes_;
|
||||
HorzSegmentList horz_seg_list_;
|
||||
std::vector<HorzJoin> horz_join_list_;
|
||||
void Reset();
|
||||
inline void InsertScanline(int64_t y);
|
||||
inline bool PopScanline(int64_t &y);
|
||||
inline bool PopLocalMinima(int64_t y, LocalMinima*& local_minima);
|
||||
void DisposeAllOutRecs();
|
||||
void DisposeVerticesAndLocalMinima();
|
||||
void DeleteEdges(Active*& e);
|
||||
inline void AddLocMin(Vertex &vert, PathType polytype, bool is_open);
|
||||
bool IsContributingClosed(const Active &e) const;
|
||||
inline bool IsContributingOpen(const Active &e) const;
|
||||
void SetWindCountForClosedPathEdge(Active &edge);
|
||||
void SetWindCountForOpenPathEdge(Active &e);
|
||||
void InsertLocalMinimaIntoAEL(int64_t bot_y);
|
||||
void InsertLeftEdge(Active &e);
|
||||
inline void PushHorz(Active &e);
|
||||
inline bool PopHorz(Active *&e);
|
||||
inline OutPt* StartOpenPath(Active &e, const Point64& pt);
|
||||
inline void UpdateEdgeIntoAEL(Active *e);
|
||||
void IntersectEdges(Active &e1, Active &e2, const Point64& pt);
|
||||
inline void DeleteFromAEL(Active &e);
|
||||
inline void AdjustCurrXAndCopyToSEL(const int64_t top_y);
|
||||
void DoIntersections(const int64_t top_y);
|
||||
void AddNewIntersectNode(Active &e1, Active &e2, const int64_t top_y);
|
||||
bool BuildIntersectList(const int64_t top_y);
|
||||
void ProcessIntersectList();
|
||||
void SwapPositionsInAEL(Active& edge1, Active& edge2);
|
||||
OutRec* NewOutRec();
|
||||
OutPt* AddOutPt(const Active &e, const Point64& pt);
|
||||
OutPt* AddLocalMinPoly(Active &e1, Active &e2,
|
||||
const Point64& pt, bool is_new = false);
|
||||
OutPt* AddLocalMaxPoly(Active &e1, Active &e2, const Point64& pt);
|
||||
void DoHorizontal(Active &horz);
|
||||
bool ResetHorzDirection(const Active &horz, const Vertex* max_vertex,
|
||||
int64_t &horz_left, int64_t &horz_right);
|
||||
void DoTopOfScanbeam(const int64_t top_y);
|
||||
Active *DoMaxima(Active &e);
|
||||
void JoinOutrecPaths(Active &e1, Active &e2);
|
||||
void FixSelfIntersects(OutRec* outrec);
|
||||
void DoSplitOp(OutRec* outRec, OutPt* splitOp);
|
||||
|
||||
inline void AddTrialHorzJoin(OutPt* op);
|
||||
void ConvertHorzSegsToJoins();
|
||||
void ProcessHorzJoins();
|
||||
|
||||
void Split(Active& e, const Point64& pt);
|
||||
inline void CheckJoinLeft(Active& e,
|
||||
const Point64& pt, bool check_curr_x = false);
|
||||
inline void CheckJoinRight(Active& e,
|
||||
const Point64& pt, bool check_curr_x = false);
|
||||
protected:
|
||||
bool preserve_collinear_ = true;
|
||||
bool reverse_solution_ = false;
|
||||
int error_code_ = 0;
|
||||
bool has_open_paths_ = false;
|
||||
bool succeeded_ = true;
|
||||
OutRecList outrec_list_; //pointers in case list memory reallocated
|
||||
bool ExecuteInternal(ClipType ct, FillRule ft, bool use_polytrees);
|
||||
void CleanCollinear(OutRec* outrec);
|
||||
bool CheckBounds(OutRec* outrec);
|
||||
bool CheckSplitOwner(OutRec* outrec, OutRecList* splits);
|
||||
void RecursiveCheckOwners(OutRec* outrec, PolyPath* polypath);
|
||||
#ifdef USINGZ
|
||||
ZCallback64 zCallback_ = nullptr;
|
||||
void SetZ(const Active& e1, const Active& e2, Point64& pt);
|
||||
#endif
|
||||
void CleanUp(); // unlike Clear, CleanUp preserves added paths
|
||||
void AddPath(const Path64& path, PathType polytype, bool is_open);
|
||||
void AddPaths(const Paths64& paths, PathType polytype, bool is_open);
|
||||
public:
|
||||
virtual ~ClipperBase();
|
||||
int ErrorCode() const { return error_code_; };
|
||||
void PreserveCollinear(bool val) { preserve_collinear_ = val; };
|
||||
bool PreserveCollinear() const { return preserve_collinear_;};
|
||||
void ReverseSolution(bool val) { reverse_solution_ = val; };
|
||||
bool ReverseSolution() const { return reverse_solution_; };
|
||||
void Clear();
|
||||
void AddReuseableData(const ReuseableDataContainer64& reuseable_data);
|
||||
#ifdef USINGZ
|
||||
int64_t DefaultZ = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
// PolyPath / PolyTree --------------------------------------------------------
|
||||
|
||||
//PolyTree: is intended as a READ-ONLY data structure for CLOSED paths returned
|
||||
//by clipping operations. While this structure is more complex than the
|
||||
//alternative Paths structure, it does preserve path 'ownership' - ie those
|
||||
//paths that contain (or own) other paths. This will be useful to some users.
|
||||
|
||||
class PolyPath {
|
||||
protected:
|
||||
PolyPath* parent_;
|
||||
public:
|
||||
PolyPath(PolyPath* parent = nullptr): parent_(parent){}
|
||||
virtual ~PolyPath() {};
|
||||
//https://en.cppreference.com/w/cpp/language/rule_of_three
|
||||
PolyPath(const PolyPath&) = delete;
|
||||
PolyPath& operator=(const PolyPath&) = delete;
|
||||
|
||||
unsigned Level() const
|
||||
{
|
||||
unsigned result = 0;
|
||||
const PolyPath* p = parent_;
|
||||
while (p) { ++result; p = p->parent_; }
|
||||
return result;
|
||||
}
|
||||
|
||||
virtual PolyPath* AddChild(const Path64& path) = 0;
|
||||
|
||||
virtual void Clear() = 0;
|
||||
virtual size_t Count() const { return 0; }
|
||||
|
||||
const PolyPath* Parent() const { return parent_; }
|
||||
|
||||
bool IsHole() const
|
||||
{
|
||||
unsigned lvl = Level();
|
||||
//Even levels except level 0
|
||||
return lvl && !(lvl & 1);
|
||||
}
|
||||
};
|
||||
|
||||
typedef typename std::vector<std::unique_ptr<PolyPath64>> PolyPath64List;
|
||||
typedef typename std::vector<std::unique_ptr<PolyPathD>> PolyPathDList;
|
||||
|
||||
class PolyPath64 : public PolyPath {
|
||||
private:
|
||||
PolyPath64List childs_;
|
||||
Path64 polygon_;
|
||||
public:
|
||||
explicit PolyPath64(PolyPath64* parent = nullptr) : PolyPath(parent) {}
|
||||
explicit PolyPath64(PolyPath64* parent, const Path64& path) : PolyPath(parent) { polygon_ = path; }
|
||||
|
||||
~PolyPath64() {
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
PolyPath64* operator [] (size_t index) const
|
||||
{
|
||||
return childs_[index].get(); //std::unique_ptr
|
||||
}
|
||||
|
||||
PolyPath64* Child(size_t index) const
|
||||
{
|
||||
return childs_[index].get();
|
||||
}
|
||||
|
||||
PolyPath64List::const_iterator begin() const { return childs_.cbegin(); }
|
||||
PolyPath64List::const_iterator end() const { return childs_.cend(); }
|
||||
|
||||
PolyPath64* AddChild(const Path64& path) override
|
||||
{
|
||||
return childs_.emplace_back(std::make_unique<PolyPath64>(this, path)).get();
|
||||
}
|
||||
|
||||
void Clear() override
|
||||
{
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
size_t Count() const override
|
||||
{
|
||||
return childs_.size();
|
||||
}
|
||||
|
||||
const Path64& Polygon() const { return polygon_; };
|
||||
|
||||
double Area() const
|
||||
{
|
||||
return std::accumulate(childs_.cbegin(), childs_.cend(),
|
||||
Clipper2Lib::Area<int64_t>(polygon_),
|
||||
[](double a, const auto& child) {return a + child->Area(); });
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class PolyPathD : public PolyPath {
|
||||
private:
|
||||
PolyPathDList childs_;
|
||||
double scale_;
|
||||
PathD polygon_;
|
||||
public:
|
||||
explicit PolyPathD(PolyPathD* parent = nullptr) : PolyPath(parent)
|
||||
{
|
||||
scale_ = parent ? parent->scale_ : 1.0;
|
||||
}
|
||||
|
||||
explicit PolyPathD(PolyPathD* parent, const Path64& path) : PolyPath(parent)
|
||||
{
|
||||
scale_ = parent ? parent->scale_ : 1.0;
|
||||
int error_code = 0;
|
||||
polygon_ = ScalePath<double, int64_t>(path, scale_, error_code);
|
||||
}
|
||||
|
||||
explicit PolyPathD(PolyPathD* parent, const PathD& path) : PolyPath(parent)
|
||||
{
|
||||
scale_ = parent ? parent->scale_ : 1.0;
|
||||
polygon_ = path;
|
||||
}
|
||||
|
||||
~PolyPathD() {
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
PolyPathD* operator [] (size_t index) const
|
||||
{
|
||||
return childs_[index].get();
|
||||
}
|
||||
|
||||
PolyPathD* Child(size_t index) const
|
||||
{
|
||||
return childs_[index].get();
|
||||
}
|
||||
|
||||
PolyPathDList::const_iterator begin() const { return childs_.cbegin(); }
|
||||
PolyPathDList::const_iterator end() const { return childs_.cend(); }
|
||||
|
||||
void SetScale(double value) { scale_ = value; }
|
||||
double Scale() const { return scale_; }
|
||||
|
||||
PolyPathD* AddChild(const Path64& path) override
|
||||
{
|
||||
return childs_.emplace_back(std::make_unique<PolyPathD>(this, path)).get();
|
||||
}
|
||||
|
||||
PolyPathD* AddChild(const PathD& path)
|
||||
{
|
||||
return childs_.emplace_back(std::make_unique<PolyPathD>(this, path)).get();
|
||||
}
|
||||
|
||||
void Clear() override
|
||||
{
|
||||
childs_.resize(0);
|
||||
}
|
||||
|
||||
size_t Count() const override
|
||||
{
|
||||
return childs_.size();
|
||||
}
|
||||
|
||||
const PathD& Polygon() const { return polygon_; };
|
||||
|
||||
double Area() const
|
||||
{
|
||||
return std::accumulate(childs_.begin(), childs_.end(),
|
||||
Clipper2Lib::Area<double>(polygon_),
|
||||
[](double a, const auto& child) {return a + child->Area(); });
|
||||
}
|
||||
};
|
||||
|
||||
class Clipper64 : public ClipperBase
|
||||
{
|
||||
private:
|
||||
void BuildPaths64(Paths64& solutionClosed, Paths64* solutionOpen);
|
||||
void BuildTree64(PolyPath64& polytree, Paths64& open_paths);
|
||||
public:
|
||||
#ifdef USINGZ
|
||||
void SetZCallback(ZCallback64 cb) { zCallback_ = cb; }
|
||||
#endif
|
||||
|
||||
void AddSubject(const Paths64& subjects)
|
||||
{
|
||||
AddPaths(subjects, PathType::Subject, false);
|
||||
}
|
||||
void AddOpenSubject(const Paths64& open_subjects)
|
||||
{
|
||||
AddPaths(open_subjects, PathType::Subject, true);
|
||||
}
|
||||
void AddClip(const Paths64& clips)
|
||||
{
|
||||
AddPaths(clips, PathType::Clip, false);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type,
|
||||
FillRule fill_rule, Paths64& closed_paths)
|
||||
{
|
||||
Paths64 dummy;
|
||||
return Execute(clip_type, fill_rule, closed_paths, dummy);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type, FillRule fill_rule,
|
||||
Paths64& closed_paths, Paths64& open_paths)
|
||||
{
|
||||
closed_paths.clear();
|
||||
open_paths.clear();
|
||||
if (ExecuteInternal(clip_type, fill_rule, false))
|
||||
BuildPaths64(closed_paths, &open_paths);
|
||||
CleanUp();
|
||||
return succeeded_;
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type, FillRule fill_rule, PolyTree64& polytree)
|
||||
{
|
||||
Paths64 dummy;
|
||||
return Execute(clip_type, fill_rule, polytree, dummy);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type,
|
||||
FillRule fill_rule, PolyTree64& polytree, Paths64& open_paths)
|
||||
{
|
||||
if (ExecuteInternal(clip_type, fill_rule, true))
|
||||
{
|
||||
open_paths.clear();
|
||||
polytree.Clear();
|
||||
BuildTree64(polytree, open_paths);
|
||||
}
|
||||
CleanUp();
|
||||
return succeeded_;
|
||||
}
|
||||
};
|
||||
|
||||
class ClipperD : public ClipperBase {
|
||||
private:
|
||||
double scale_ = 1.0, invScale_ = 1.0;
|
||||
#ifdef USINGZ
|
||||
ZCallbackD zCallbackD_ = nullptr;
|
||||
#endif
|
||||
void BuildPathsD(PathsD& solutionClosed, PathsD* solutionOpen);
|
||||
void BuildTreeD(PolyPathD& polytree, PathsD& open_paths);
|
||||
public:
|
||||
explicit ClipperD(int precision = 2) : ClipperBase()
|
||||
{
|
||||
CheckPrecisionRange(precision, error_code_);
|
||||
// to optimize scaling / descaling precision
|
||||
// set the scale to a power of double's radix (2) (#25)
|
||||
scale_ = std::pow(std::numeric_limits<double>::radix,
|
||||
std::ilogb(std::pow(10, precision)) + 1);
|
||||
invScale_ = 1 / scale_;
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
void SetZCallback(ZCallbackD cb) { zCallbackD_ = cb; };
|
||||
|
||||
void ZCB(const Point64& e1bot, const Point64& e1top,
|
||||
const Point64& e2bot, const Point64& e2top, Point64& pt)
|
||||
{
|
||||
// de-scale (x & y)
|
||||
// temporarily convert integers to their initial float values
|
||||
// this will slow clipping marginally but will make it much easier
|
||||
// to understand the coordinates passed to the callback function
|
||||
PointD tmp = PointD(pt) * invScale_;
|
||||
PointD e1b = PointD(e1bot) * invScale_;
|
||||
PointD e1t = PointD(e1top) * invScale_;
|
||||
PointD e2b = PointD(e2bot) * invScale_;
|
||||
PointD e2t = PointD(e2top) * invScale_;
|
||||
zCallbackD_(e1b,e1t, e2b, e2t, tmp);
|
||||
pt.z = tmp.z; // only update 'z'
|
||||
};
|
||||
|
||||
void CheckCallback()
|
||||
{
|
||||
if(zCallbackD_)
|
||||
// if the user defined float point callback has been assigned
|
||||
// then assign the proxy callback function
|
||||
ClipperBase::zCallback_ =
|
||||
std::bind(&ClipperD::ZCB, this, std::placeholders::_1,
|
||||
std::placeholders::_2, std::placeholders::_3,
|
||||
std::placeholders::_4, std::placeholders::_5);
|
||||
else
|
||||
ClipperBase::zCallback_ = nullptr;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void AddSubject(const PathsD& subjects)
|
||||
{
|
||||
AddPaths(ScalePaths<int64_t, double>(subjects, scale_, error_code_), PathType::Subject, false);
|
||||
}
|
||||
|
||||
void AddOpenSubject(const PathsD& open_subjects)
|
||||
{
|
||||
AddPaths(ScalePaths<int64_t, double>(open_subjects, scale_, error_code_), PathType::Subject, true);
|
||||
}
|
||||
|
||||
void AddClip(const PathsD& clips)
|
||||
{
|
||||
AddPaths(ScalePaths<int64_t, double>(clips, scale_, error_code_), PathType::Clip, false);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type, FillRule fill_rule, PathsD& closed_paths)
|
||||
{
|
||||
PathsD dummy;
|
||||
return Execute(clip_type, fill_rule, closed_paths, dummy);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type,
|
||||
FillRule fill_rule, PathsD& closed_paths, PathsD& open_paths)
|
||||
{
|
||||
#ifdef USINGZ
|
||||
CheckCallback();
|
||||
#endif
|
||||
if (ExecuteInternal(clip_type, fill_rule, false))
|
||||
{
|
||||
BuildPathsD(closed_paths, &open_paths);
|
||||
}
|
||||
CleanUp();
|
||||
return succeeded_;
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type, FillRule fill_rule, PolyTreeD& polytree)
|
||||
{
|
||||
PathsD dummy;
|
||||
return Execute(clip_type, fill_rule, polytree, dummy);
|
||||
}
|
||||
|
||||
bool Execute(ClipType clip_type,
|
||||
FillRule fill_rule, PolyTreeD& polytree, PathsD& open_paths)
|
||||
{
|
||||
#ifdef USINGZ
|
||||
CheckCallback();
|
||||
#endif
|
||||
if (ExecuteInternal(clip_type, fill_rule, true))
|
||||
{
|
||||
polytree.Clear();
|
||||
polytree.SetScale(invScale_);
|
||||
open_paths.clear();
|
||||
BuildTreeD(polytree, open_paths);
|
||||
}
|
||||
CleanUp();
|
||||
return succeeded_;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
#endif // CLIPPER_ENGINE_H
|
||||
@@ -0,0 +1,827 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 17 September 2024 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Purpose : This module exports the Clipper2 Library (ie DLL/so) *
|
||||
* License : http://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
|
||||
/*
|
||||
Boolean clipping:
|
||||
cliptype: NoClip=0, Intersection=1, Union=2, Difference=3, Xor=4
|
||||
fillrule: EvenOdd=0, NonZero=1, Positive=2, Negative=3
|
||||
|
||||
Polygon offsetting (inflate/deflate):
|
||||
jointype: Square=0, Bevel=1, Round=2, Miter=3
|
||||
endtype: Polygon=0, Joined=1, Butt=2, Square=3, Round=4
|
||||
|
||||
The path structures used extensively in other parts of this library are all
|
||||
based on std::vector classes. Since C++ classes can't be accessed by other
|
||||
languages, these paths are converted here into very simple array data
|
||||
structures that can be parsed by just about any programming language.
|
||||
|
||||
These 2D paths are defined by series of x and y coordinates together with an
|
||||
optional user-defined 'z' value (see Z-values below). Hence, a vertex refers
|
||||
to a single x and y coordinate (+/- a user-defined value). These values will
|
||||
be either type int64_t or type double. Data structures have names that
|
||||
indicate the array type by a suffixed '64' or a 'D'. For example, the data
|
||||
structure CPath64 contains an array of int64_t values, whereas the data
|
||||
structure CPathD contains an array of double. Where documentation omits
|
||||
the type suffix (eg CPath), it is simply agnostic to the array's data type.
|
||||
|
||||
For conciseness, the following letters are used in the diagrams below:
|
||||
N: Number of vertices in a given path
|
||||
C: Count of structure's paths
|
||||
A: Array size (as distinct from the size in memory)
|
||||
|
||||
|
||||
CPath64 and CPathD:
|
||||
These are arrays of consecutive vertices preceeded by a pair of values
|
||||
containing the number of vertices (N) in the path, and a 0 value.
|
||||
_______________________________________________________________
|
||||
| counters | vertex1 | vertex2 | ... | vertexN |
|
||||
| N, 0 | x1, y1, (z1) | x2, y2, (z2) | ... | xN, yN, (zN) |
|
||||
---------------------------------------------------------------
|
||||
|
||||
|
||||
CPaths64 and CPathsD:
|
||||
These are also arrays containing any number of consecutive CPath
|
||||
structures. Preceeding these consecutive paths, there is a pair of
|
||||
values that contain the length of the array structure (A) and
|
||||
the count of following CPath structures (C).
|
||||
Memory allocation for CPaths64 = A * sizeof(int64_t)
|
||||
Memory allocation for CPathsD = A * sizeof(double)
|
||||
__________________________________________
|
||||
| counters | path1 | path2 | ... | pathC |
|
||||
| A, C | | | ... | |
|
||||
------------------------------------------
|
||||
|
||||
|
||||
CPolytree64 and CPolytreeD:
|
||||
These structures consist of two values followed by a series of CPolyPath
|
||||
structures. The first value indicates the total length of the array (A).
|
||||
The second value indicates the number of following CPolyPath structures
|
||||
that are the top level CPolyPath in the CPolytree (C). These CPolyPath
|
||||
may, in turn, contain their own nested CPolyPath children that
|
||||
collectively make a tree structure.
|
||||
_________________________________________________________
|
||||
| counters | CPolyPath1 | CPolyPath2 | ... | CPolyPathC |
|
||||
| A, C | | | ... | |
|
||||
---------------------------------------------------------
|
||||
|
||||
|
||||
CPolyPath64 and CPolyPathD:
|
||||
These array structures consist of a pair of counter values followed by a
|
||||
series of polygon vertices and a series of nested CPolyPath children.
|
||||
The first counter values indicates the number of vertices in the
|
||||
polygon (N), and the second counter indicates the CPolyPath child count (C).
|
||||
_____________________________________________________________________________
|
||||
|cntrs |vertex1 |vertex2 |...|vertexN |child1|child2|...|childC|
|
||||
|N, C |x1, y1, (z1)| x2, y2, (z2)|...|xN, yN, (zN)| | |...| |
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
DisposeArray64 & DisposeArrayD:
|
||||
All array structures are allocated in heap memory which will eventually
|
||||
need to be released. However, since applications linking to these DLL
|
||||
functions may use different memory managers, the only safe way to release
|
||||
this memory is to use the exported DisposeArray functions.
|
||||
|
||||
|
||||
(Optional) Z-Values:
|
||||
Structures will only contain user-defined z-values when the USINGZ
|
||||
pre-processor identifier is used. The library does not assign z-values
|
||||
because this field is intended for users to assign custom values to vertices.
|
||||
Z-values in input paths (subject and clip) will be copied to solution paths.
|
||||
New vertices at path intersections will generate a callback event that allows
|
||||
users to assign z-values at these new vertices. The user's callback function
|
||||
must conform with the DLLZCallback definition and be registered with the
|
||||
DLL via SetZCallback. To assist the user in assigning z-values, the library
|
||||
passes in the callback function the new intersection point together with
|
||||
the four vertices that define the two segments that are intersecting.
|
||||
|
||||
*/
|
||||
#ifndef CLIPPER2_EXPORT_H
|
||||
#define CLIPPER2_EXPORT_H
|
||||
|
||||
#include <cstdlib>
|
||||
#include <vector>
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.engine.h"
|
||||
#include "clipper2/clipper.offset.h"
|
||||
#include "clipper2/clipper.rectclip.h"
|
||||
|
||||
namespace Clipper2Lib {
|
||||
|
||||
typedef int64_t* CPath64;
|
||||
typedef int64_t* CPaths64;
|
||||
typedef double* CPathD;
|
||||
typedef double* CPathsD;
|
||||
|
||||
typedef int64_t* CPolyPath64;
|
||||
typedef int64_t* CPolyTree64;
|
||||
typedef double* CPolyPathD;
|
||||
typedef double* CPolyTreeD;
|
||||
|
||||
template <typename T>
|
||||
struct CRect {
|
||||
T left;
|
||||
T top;
|
||||
T right;
|
||||
T bottom;
|
||||
};
|
||||
|
||||
typedef CRect<int64_t> CRect64;
|
||||
typedef CRect<double> CRectD;
|
||||
|
||||
template <typename T>
|
||||
inline bool CRectIsEmpty(const CRect<T>& rect)
|
||||
{
|
||||
return (rect.right <= rect.left) || (rect.bottom <= rect.top);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Rect<T> CRectToRect(const CRect<T>& rect)
|
||||
{
|
||||
Rect<T> result;
|
||||
result.left = rect.left;
|
||||
result.top = rect.top;
|
||||
result.right = rect.right;
|
||||
result.bottom = rect.bottom;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T1, typename T2>
|
||||
inline T1 Reinterpret(T2 value) {
|
||||
return *reinterpret_cast<T1*>(&value);
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)
|
||||
#else
|
||||
#define EXTERN_DLL_EXPORT extern "C"
|
||||
#endif
|
||||
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// EXPORTED FUNCTION DECLARATIONS
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
EXTERN_DLL_EXPORT const char* Version();
|
||||
|
||||
EXTERN_DLL_EXPORT void DisposeArray64(int64_t*& p)
|
||||
{
|
||||
delete[] p;
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT void DisposeArrayD(double*& p)
|
||||
{
|
||||
delete[] p;
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPaths64 subjects,
|
||||
const CPaths64 subjects_open, const CPaths64 clips,
|
||||
CPaths64& solution, CPaths64& solution_open,
|
||||
bool preserve_collinear = true, bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPaths64 subjects,
|
||||
const CPaths64 subjects_open, const CPaths64 clips,
|
||||
CPolyTree64& sol_tree, CPaths64& solution_open,
|
||||
bool preserve_collinear = true, bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPathsD subjects,
|
||||
const CPathsD subjects_open, const CPathsD clips,
|
||||
CPathsD& solution, CPathsD& solution_open, int precision = 2,
|
||||
bool preserve_collinear = true, bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPathsD subjects,
|
||||
const CPathsD subjects_open, const CPathsD clips,
|
||||
CPolyTreeD& solution, CPathsD& solution_open, int precision = 2,
|
||||
bool preserve_collinear = true, bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
|
||||
double delta, uint8_t jointype, uint8_t endtype,
|
||||
double miter_limit = 2.0, double arc_tolerance = 0.0,
|
||||
bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
|
||||
double delta, uint8_t jointype, uint8_t endtype,
|
||||
int precision = 2, double miter_limit = 2.0,
|
||||
double arc_tolerance = 0.0, bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 InflatePath64(const CPath64 path,
|
||||
double delta, uint8_t jointype, uint8_t endtype,
|
||||
double miter_limit = 2.0, double arc_tolerance = 0.0,
|
||||
bool reverse_solution = false);
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD InflatePathD(const CPathD path,
|
||||
double delta, uint8_t jointype, uint8_t endtype,
|
||||
int precision = 2, double miter_limit = 2.0,
|
||||
double arc_tolerance = 0.0, bool reverse_solution = false);
|
||||
|
||||
// RectClip & RectClipLines:
|
||||
EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect,
|
||||
const CPaths64 paths);
|
||||
EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect,
|
||||
const CPathsD paths, int precision = 2);
|
||||
EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
|
||||
const CPaths64 paths);
|
||||
EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
|
||||
const CPathsD paths, int precision = 2);
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// INTERNAL FUNCTIONS
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
#ifdef USINGZ
|
||||
ZCallback64 dllCallback64 = nullptr;
|
||||
ZCallbackD dllCallbackD = nullptr;
|
||||
|
||||
constexpr int EXPORT_VERTEX_DIMENSIONALITY = 3;
|
||||
#else
|
||||
constexpr int EXPORT_VERTEX_DIMENSIONALITY = 2;
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
static void GetPathCountAndCPathsArrayLen(const Paths<T>& paths,
|
||||
size_t& cnt, size_t& array_len)
|
||||
{
|
||||
array_len = 2;
|
||||
cnt = 0;
|
||||
for (const Path<T>& path : paths)
|
||||
if (path.size())
|
||||
{
|
||||
array_len += path.size() * EXPORT_VERTEX_DIMENSIONALITY + 2;
|
||||
++cnt;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t GetPolyPathArrayLen64(const PolyPath64& pp)
|
||||
{
|
||||
size_t result = 2; // poly_length + child_count
|
||||
result += pp.Polygon().size() * EXPORT_VERTEX_DIMENSIONALITY;
|
||||
//plus nested children :)
|
||||
for (size_t i = 0; i < pp.Count(); ++i)
|
||||
result += GetPolyPathArrayLen64(*pp[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static size_t GetPolyPathArrayLenD(const PolyPathD& pp)
|
||||
{
|
||||
size_t result = 2; // poly_length + child_count
|
||||
result += pp.Polygon().size() * EXPORT_VERTEX_DIMENSIONALITY;
|
||||
//plus nested children :)
|
||||
for (size_t i = 0; i < pp.Count(); ++i)
|
||||
result += GetPolyPathArrayLenD(*pp[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
static void GetPolytreeCountAndCStorageSize64(const PolyTree64& tree,
|
||||
size_t& cnt, size_t& array_len)
|
||||
{
|
||||
cnt = tree.Count(); // nb: top level count only
|
||||
array_len = GetPolyPathArrayLen64(tree);
|
||||
}
|
||||
|
||||
static void GetPolytreeCountAndCStorageSizeD(const PolyTreeD& tree,
|
||||
size_t& cnt, size_t& array_len)
|
||||
{
|
||||
cnt = tree.Count(); // nb: top level count only
|
||||
array_len = GetPolyPathArrayLenD(tree);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static T* CreateCPathsFromPathsT(const Paths<T>& paths)
|
||||
{
|
||||
size_t cnt = 0, array_len = 0;
|
||||
GetPathCountAndCPathsArrayLen(paths, cnt, array_len);
|
||||
T* result = new T[array_len], * v = result;
|
||||
*v++ = array_len;
|
||||
*v++ = cnt;
|
||||
for (const Path<T>& path : paths)
|
||||
{
|
||||
if (!path.size()) continue;
|
||||
*v++ = path.size();
|
||||
*v++ = 0;
|
||||
for (const Point<T>& pt : path)
|
||||
{
|
||||
*v++ = pt.x;
|
||||
*v++ = pt.y;
|
||||
#ifdef USINGZ
|
||||
*v++ = Reinterpret<T>(pt.z);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CPathsD CreateCPathsDFromPathsD(const PathsD& paths)
|
||||
{
|
||||
if (!paths.size()) return nullptr;
|
||||
size_t cnt, array_len;
|
||||
GetPathCountAndCPathsArrayLen(paths, cnt, array_len);
|
||||
CPathsD result = new double[array_len], v = result;
|
||||
*v++ = (double)array_len;
|
||||
*v++ = (double)cnt;
|
||||
for (const PathD& path : paths)
|
||||
{
|
||||
if (!path.size()) continue;
|
||||
*v = (double)path.size();
|
||||
++v; *v++ = 0;
|
||||
for (const PointD& pt : path)
|
||||
{
|
||||
*v++ = pt.x;
|
||||
*v++ = pt.y;
|
||||
#ifdef USINGZ
|
||||
* v++ = Reinterpret<double>(pt.z);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
CPathsD CreateCPathsDFromPaths64(const Paths64& paths, double scale)
|
||||
{
|
||||
if (!paths.size()) return nullptr;
|
||||
size_t cnt, array_len;
|
||||
GetPathCountAndCPathsArrayLen(paths, cnt, array_len);
|
||||
CPathsD result = new double[array_len], v = result;
|
||||
*v++ = (double)array_len;
|
||||
*v++ = (double)cnt;
|
||||
for (const Path64& path : paths)
|
||||
{
|
||||
if (!path.size()) continue;
|
||||
*v = (double)path.size();
|
||||
++v; *v++ = 0;
|
||||
for (const Point64& pt : path)
|
||||
{
|
||||
*v++ = pt.x * scale;
|
||||
*v++ = pt.y * scale;
|
||||
#ifdef USINGZ
|
||||
*v++ = Reinterpret<double>(pt.z);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static Path<T> ConvertCPathToPathT(T* path)
|
||||
{
|
||||
Path<T> result;
|
||||
if (!path) return result;
|
||||
T* v = path;
|
||||
size_t cnt = static_cast<size_t>(*v);
|
||||
v += 2; // skip 0 value
|
||||
result.reserve(cnt);
|
||||
for (size_t j = 0; j < cnt; ++j)
|
||||
{
|
||||
T x = *v++, y = *v++;
|
||||
#ifdef USINGZ
|
||||
z_type z = Reinterpret<z_type>(*v++);
|
||||
result.push_back(Point<T>(x, y, z));
|
||||
#else
|
||||
result.push_back(Point<T>(x, y));
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
static Paths<T> ConvertCPathsToPathsT(T* paths)
|
||||
{
|
||||
Paths<T> result;
|
||||
if (!paths) return result;
|
||||
T* v = paths; ++v;
|
||||
size_t cnt = static_cast<size_t>(*v++);
|
||||
result.reserve(cnt);
|
||||
for (size_t i = 0; i < cnt; ++i)
|
||||
{
|
||||
size_t cnt2 = static_cast<size_t>(*v);
|
||||
v += 2;
|
||||
Path<T> path;
|
||||
path.reserve(cnt2);
|
||||
for (size_t j = 0; j < cnt2; ++j)
|
||||
{
|
||||
T x = *v++, y = *v++;
|
||||
#ifdef USINGZ
|
||||
z_type z = Reinterpret<z_type>(*v++);
|
||||
path.push_back(Point<T>(x, y, z));
|
||||
#else
|
||||
path.push_back(Point<T>(x, y));
|
||||
#endif
|
||||
}
|
||||
result.push_back(path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static Path64 ConvertCPathDToPath64WithScale(const CPathD path, double scale)
|
||||
{
|
||||
Path64 result;
|
||||
if (!path) return result;
|
||||
double* v = path;
|
||||
size_t cnt = static_cast<size_t>(*v);
|
||||
v += 2; // skip 0 value
|
||||
result.reserve(cnt);
|
||||
for (size_t j = 0; j < cnt; ++j)
|
||||
{
|
||||
double x = *v++ * scale;
|
||||
double y = *v++ * scale;
|
||||
#ifdef USINGZ
|
||||
z_type z = Reinterpret<z_type>(*v++);
|
||||
result.push_back(Point64(x, y, z));
|
||||
#else
|
||||
result.push_back(Point64(x, y));
|
||||
#endif
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static Paths64 ConvertCPathsDToPaths64(const CPathsD paths, double scale)
|
||||
{
|
||||
Paths64 result;
|
||||
if (!paths) return result;
|
||||
double* v = paths;
|
||||
++v; // skip the first value (0)
|
||||
size_t cnt = static_cast<size_t>(*v++);
|
||||
result.reserve(cnt);
|
||||
for (size_t i = 0; i < cnt; ++i)
|
||||
{
|
||||
size_t cnt2 = static_cast<size_t>(*v);
|
||||
v += 2;
|
||||
Path64 path;
|
||||
path.reserve(cnt2);
|
||||
for (size_t j = 0; j < cnt2; ++j)
|
||||
{
|
||||
double x = *v++ * scale;
|
||||
double y = *v++ * scale;
|
||||
#ifdef USINGZ
|
||||
z_type z = Reinterpret<z_type>(*v++);
|
||||
path.push_back(Point64(x, y, z));
|
||||
#else
|
||||
path.push_back(Point64(x, y));
|
||||
#endif
|
||||
}
|
||||
result.push_back(path);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void CreateCPolyPath64(const PolyPath64* pp, int64_t*& v)
|
||||
{
|
||||
*v++ = static_cast<int64_t>(pp->Polygon().size());
|
||||
*v++ = static_cast<int64_t>(pp->Count());
|
||||
for (const Point64& pt : pp->Polygon())
|
||||
{
|
||||
*v++ = pt.x;
|
||||
*v++ = pt.y;
|
||||
#ifdef USINGZ
|
||||
* v++ = Reinterpret<int64_t>(pt.z); // raw memory copy
|
||||
#endif
|
||||
}
|
||||
for (size_t i = 0; i < pp->Count(); ++i)
|
||||
CreateCPolyPath64(pp->Child(i), v);
|
||||
}
|
||||
|
||||
static void CreateCPolyPathD(const PolyPathD* pp, double*& v)
|
||||
{
|
||||
*v++ = static_cast<double>(pp->Polygon().size());
|
||||
*v++ = static_cast<double>(pp->Count());
|
||||
for (const PointD& pt : pp->Polygon())
|
||||
{
|
||||
*v++ = pt.x;
|
||||
*v++ = pt.y;
|
||||
#ifdef USINGZ
|
||||
* v++ = Reinterpret<double>(pt.z); // raw memory copy
|
||||
#endif
|
||||
}
|
||||
for (size_t i = 0; i < pp->Count(); ++i)
|
||||
CreateCPolyPathD(pp->Child(i), v);
|
||||
}
|
||||
|
||||
static int64_t* CreateCPolyTree64(const PolyTree64& tree)
|
||||
{
|
||||
size_t cnt, array_len;
|
||||
GetPolytreeCountAndCStorageSize64(tree, cnt, array_len);
|
||||
if (!cnt) return nullptr;
|
||||
// allocate storage
|
||||
int64_t* result = new int64_t[array_len];
|
||||
int64_t* v = result;
|
||||
*v++ = static_cast<int64_t>(array_len);
|
||||
*v++ = static_cast<int64_t>(tree.Count());
|
||||
for (size_t i = 0; i < tree.Count(); ++i)
|
||||
CreateCPolyPath64(tree.Child(i), v);
|
||||
return result;
|
||||
}
|
||||
|
||||
static double* CreateCPolyTreeD(const PolyTreeD& tree)
|
||||
{
|
||||
double scale = std::log10(tree.Scale());
|
||||
size_t cnt, array_len;
|
||||
GetPolytreeCountAndCStorageSizeD(tree, cnt, array_len);
|
||||
if (!cnt) return nullptr;
|
||||
// allocate storage
|
||||
double* result = new double[array_len];
|
||||
double* v = result;
|
||||
*v++ = static_cast<double>(array_len);
|
||||
*v++ = static_cast<double>(tree.Count());
|
||||
for (size_t i = 0; i < tree.Count(); ++i)
|
||||
CreateCPolyPathD(tree.Child(i), v);
|
||||
return result;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
// EXPORTED FUNCTION DEFINITIONS
|
||||
//////////////////////////////////////////////////////
|
||||
|
||||
EXTERN_DLL_EXPORT const char* Version()
|
||||
{
|
||||
return CLIPPER2_VERSION;
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOp64(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPaths64 subjects,
|
||||
const CPaths64 subjects_open, const CPaths64 clips,
|
||||
CPaths64& solution, CPaths64& solution_open,
|
||||
bool preserve_collinear, bool reverse_solution)
|
||||
{
|
||||
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
|
||||
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
|
||||
|
||||
Paths64 sub, sub_open, clp, sol, sol_open;
|
||||
sub = ConvertCPathsToPathsT(subjects);
|
||||
sub_open = ConvertCPathsToPathsT(subjects_open);
|
||||
clp = ConvertCPathsToPathsT(clips);
|
||||
|
||||
Clipper64 clipper;
|
||||
clipper.PreserveCollinear(preserve_collinear);
|
||||
clipper.ReverseSolution(reverse_solution);
|
||||
#ifdef USINGZ
|
||||
if (dllCallback64)
|
||||
clipper.SetZCallback(dllCallback64);
|
||||
#endif
|
||||
if (sub.size() > 0) clipper.AddSubject(sub);
|
||||
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
|
||||
if (clp.size() > 0) clipper.AddClip(clp);
|
||||
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), sol, sol_open))
|
||||
return -1; // clipping bug - should never happen :)
|
||||
solution = CreateCPathsFromPathsT(sol);
|
||||
solution_open = CreateCPathsFromPathsT(sol_open);
|
||||
return 0; //success !!
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOp_PolyTree64(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPaths64 subjects,
|
||||
const CPaths64 subjects_open, const CPaths64 clips,
|
||||
CPolyTree64& sol_tree, CPaths64& solution_open,
|
||||
bool preserve_collinear, bool reverse_solution)
|
||||
{
|
||||
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
|
||||
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
|
||||
Paths64 sub, sub_open, clp, sol_open;
|
||||
sub = ConvertCPathsToPathsT(subjects);
|
||||
sub_open = ConvertCPathsToPathsT(subjects_open);
|
||||
clp = ConvertCPathsToPathsT(clips);
|
||||
|
||||
PolyTree64 tree;
|
||||
Clipper64 clipper;
|
||||
clipper.PreserveCollinear(preserve_collinear);
|
||||
clipper.ReverseSolution(reverse_solution);
|
||||
#ifdef USINGZ
|
||||
if (dllCallback64)
|
||||
clipper.SetZCallback(dllCallback64);
|
||||
#endif
|
||||
if (sub.size() > 0) clipper.AddSubject(sub);
|
||||
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
|
||||
if (clp.size() > 0) clipper.AddClip(clp);
|
||||
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open))
|
||||
return -1; // clipping bug - should never happen :)
|
||||
|
||||
sol_tree = CreateCPolyTree64(tree);
|
||||
solution_open = CreateCPathsFromPathsT(sol_open);
|
||||
return 0; //success !!
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOpD(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPathsD subjects,
|
||||
const CPathsD subjects_open, const CPathsD clips,
|
||||
CPathsD& solution, CPathsD& solution_open, int precision,
|
||||
bool preserve_collinear, bool reverse_solution)
|
||||
{
|
||||
if (precision < -8 || precision > 8) return -5;
|
||||
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
|
||||
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
|
||||
//const double scale = std::pow(10, precision);
|
||||
|
||||
PathsD sub, sub_open, clp, sol, sol_open;
|
||||
sub = ConvertCPathsToPathsT(subjects);
|
||||
sub_open = ConvertCPathsToPathsT(subjects_open);
|
||||
clp = ConvertCPathsToPathsT(clips);
|
||||
|
||||
ClipperD clipper(precision);
|
||||
clipper.PreserveCollinear(preserve_collinear);
|
||||
clipper.ReverseSolution(reverse_solution);
|
||||
#ifdef USINGZ
|
||||
if (dllCallbackD)
|
||||
clipper.SetZCallback(dllCallbackD);
|
||||
#endif
|
||||
if (sub.size() > 0) clipper.AddSubject(sub);
|
||||
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
|
||||
if (clp.size() > 0) clipper.AddClip(clp);
|
||||
if (!clipper.Execute(ClipType(cliptype),
|
||||
FillRule(fillrule), sol, sol_open)) return -1;
|
||||
solution = CreateCPathsDFromPathsD(sol);
|
||||
solution_open = CreateCPathsDFromPathsD(sol_open);
|
||||
return 0;
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT int BooleanOp_PolyTreeD(uint8_t cliptype,
|
||||
uint8_t fillrule, const CPathsD subjects,
|
||||
const CPathsD subjects_open, const CPathsD clips,
|
||||
CPolyTreeD& solution, CPathsD& solution_open, int precision,
|
||||
bool preserve_collinear, bool reverse_solution)
|
||||
{
|
||||
if (precision < -8 || precision > 8) return -5;
|
||||
if (cliptype > static_cast<uint8_t>(ClipType::Xor)) return -4;
|
||||
if (fillrule > static_cast<uint8_t>(FillRule::Negative)) return -3;
|
||||
//double scale = std::pow(10, precision);
|
||||
|
||||
int err = 0;
|
||||
PathsD sub, sub_open, clp, sol_open;
|
||||
sub = ConvertCPathsToPathsT(subjects);
|
||||
sub_open = ConvertCPathsToPathsT(subjects_open);
|
||||
clp = ConvertCPathsToPathsT(clips);
|
||||
|
||||
PolyTreeD tree;
|
||||
ClipperD clipper(precision);
|
||||
clipper.PreserveCollinear(preserve_collinear);
|
||||
clipper.ReverseSolution(reverse_solution);
|
||||
#ifdef USINGZ
|
||||
if (dllCallbackD)
|
||||
clipper.SetZCallback(dllCallbackD);
|
||||
#endif
|
||||
if (sub.size() > 0) clipper.AddSubject(sub);
|
||||
if (sub_open.size() > 0) clipper.AddOpenSubject(sub_open);
|
||||
if (clp.size() > 0) clipper.AddClip(clp);
|
||||
if (!clipper.Execute(ClipType(cliptype), FillRule(fillrule), tree, sol_open))
|
||||
return -1; // clipping bug - should never happen :)
|
||||
|
||||
solution = CreateCPolyTreeD(tree);
|
||||
solution_open = CreateCPathsDFromPathsD(sol_open);
|
||||
return 0; //success !!
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 InflatePaths64(const CPaths64 paths,
|
||||
double delta, uint8_t jointype, uint8_t endtype, double miter_limit,
|
||||
double arc_tolerance, bool reverse_solution)
|
||||
{
|
||||
Paths64 pp;
|
||||
pp = ConvertCPathsToPathsT(paths);
|
||||
ClipperOffset clip_offset( miter_limit,
|
||||
arc_tolerance, reverse_solution);
|
||||
clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
|
||||
Paths64 result;
|
||||
clip_offset.Execute(delta, result);
|
||||
return CreateCPathsFromPathsT(result);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD InflatePathsD(const CPathsD paths,
|
||||
double delta, uint8_t jointype, uint8_t endtype,
|
||||
int precision, double miter_limit,
|
||||
double arc_tolerance, bool reverse_solution)
|
||||
{
|
||||
if (precision < -8 || precision > 8 || !paths) return nullptr;
|
||||
|
||||
const double scale = std::pow(10, precision);
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution);
|
||||
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
|
||||
clip_offset.AddPaths(pp, JoinType(jointype), EndType(endtype));
|
||||
Paths64 result;
|
||||
clip_offset.Execute(delta * scale, result);
|
||||
return CreateCPathsDFromPaths64(result, 1 / scale);
|
||||
}
|
||||
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 InflatePath64(const CPath64 path,
|
||||
double delta, uint8_t jointype, uint8_t endtype, double miter_limit,
|
||||
double arc_tolerance, bool reverse_solution)
|
||||
{
|
||||
Path64 pp;
|
||||
pp = ConvertCPathToPathT(path);
|
||||
ClipperOffset clip_offset(miter_limit,
|
||||
arc_tolerance, reverse_solution);
|
||||
clip_offset.AddPath(pp, JoinType(jointype), EndType(endtype));
|
||||
Paths64 result;
|
||||
clip_offset.Execute(delta, result);
|
||||
return CreateCPathsFromPathsT(result);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD InflatePathD(const CPathD path,
|
||||
double delta, uint8_t jointype, uint8_t endtype,
|
||||
int precision, double miter_limit,
|
||||
double arc_tolerance, bool reverse_solution)
|
||||
{
|
||||
if (precision < -8 || precision > 8 || !path) return nullptr;
|
||||
|
||||
const double scale = std::pow(10, precision);
|
||||
ClipperOffset clip_offset(miter_limit, arc_tolerance, reverse_solution);
|
||||
Path64 pp = ConvertCPathDToPath64WithScale(path, scale);
|
||||
clip_offset.AddPath(pp, JoinType(jointype), EndType(endtype));
|
||||
Paths64 result;
|
||||
clip_offset.Execute(delta * scale, result);
|
||||
|
||||
return CreateCPathsDFromPaths64(result, 1 / scale);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 RectClip64(const CRect64& rect, const CPaths64 paths)
|
||||
{
|
||||
if (CRectIsEmpty(rect) || !paths) return nullptr;
|
||||
Rect64 r64 = CRectToRect(rect);
|
||||
class RectClip64 rc(r64);
|
||||
Paths64 pp = ConvertCPathsToPathsT(paths);
|
||||
Paths64 result = rc.Execute(pp);
|
||||
return CreateCPathsFromPathsT(result);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD RectClipD(const CRectD& rect, const CPathsD paths, int precision)
|
||||
{
|
||||
if (CRectIsEmpty(rect) || !paths) return nullptr;
|
||||
if (precision < -8 || precision > 8) return nullptr;
|
||||
const double scale = std::pow(10, precision);
|
||||
|
||||
RectD r = CRectToRect(rect);
|
||||
Rect64 rec = ScaleRect<int64_t, double>(r, scale);
|
||||
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
|
||||
class RectClip64 rc(rec);
|
||||
Paths64 result = rc.Execute(pp);
|
||||
|
||||
return CreateCPathsDFromPaths64(result, 1 / scale);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 RectClipLines64(const CRect64& rect,
|
||||
const CPaths64 paths)
|
||||
{
|
||||
if (CRectIsEmpty(rect) || !paths) return nullptr;
|
||||
Rect64 r = CRectToRect(rect);
|
||||
class RectClipLines64 rcl (r);
|
||||
Paths64 pp = ConvertCPathsToPathsT(paths);
|
||||
Paths64 result = rcl.Execute(pp);
|
||||
return CreateCPathsFromPathsT(result);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPathsD RectClipLinesD(const CRectD& rect,
|
||||
const CPathsD paths, int precision)
|
||||
{
|
||||
if (CRectIsEmpty(rect) || !paths) return nullptr;
|
||||
if (precision < -8 || precision > 8) return nullptr;
|
||||
|
||||
const double scale = std::pow(10, precision);
|
||||
Rect64 r = ScaleRect<int64_t, double>(CRectToRect(rect), scale);
|
||||
class RectClipLines64 rcl(r);
|
||||
Paths64 pp = ConvertCPathsDToPaths64(paths, scale);
|
||||
Paths64 result = rcl.Execute(pp);
|
||||
return CreateCPathsDFromPaths64(result, 1 / scale);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 MinkowskiSum64(const CPath64& cpattern, const CPath64& cpath, bool is_closed)
|
||||
{
|
||||
Path64 path = ConvertCPathToPathT(cpath);
|
||||
Path64 pattern = ConvertCPathToPathT(cpattern);
|
||||
Paths64 solution = MinkowskiSum(pattern, path, is_closed);
|
||||
return CreateCPathsFromPathsT(solution);
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT CPaths64 MinkowskiDiff64(const CPath64& cpattern, const CPath64& cpath, bool is_closed)
|
||||
{
|
||||
Path64 path = ConvertCPathToPathT(cpath);
|
||||
Path64 pattern = ConvertCPathToPathT(cpattern);
|
||||
Paths64 solution = MinkowskiDiff(pattern, path, is_closed);
|
||||
return CreateCPathsFromPathsT(solution);
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
typedef void (*DLLZCallback64)(const Point64& e1bot, const Point64& e1top, const Point64& e2bot, const Point64& e2top, Point64& pt);
|
||||
typedef void (*DLLZCallbackD)(const PointD& e1bot, const PointD& e1top, const PointD& e2bot, const PointD& e2top, PointD& pt);
|
||||
|
||||
EXTERN_DLL_EXPORT void SetZCallback64(DLLZCallback64 callback)
|
||||
{
|
||||
dllCallback64 = callback;
|
||||
}
|
||||
|
||||
EXTERN_DLL_EXPORT void SetZCallbackD(DLLZCallbackD callback)
|
||||
{
|
||||
dllCallbackD = callback;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
#endif // CLIPPER2_EXPORT_H
|
||||
@@ -0,0 +1,671 @@
|
||||
/*******************************************************************************
|
||||
* Author : Angus Johnson *
|
||||
* Date : 27 April 2024 *
|
||||
* Website : http://www.angusj.com *
|
||||
* Copyright : Angus Johnson 2010-2024 *
|
||||
* Purpose : This module provides a simple interface to the Clipper Library *
|
||||
* License : http://www.boost.org/LICENSE_1_0.txt *
|
||||
*******************************************************************************/
|
||||
|
||||
#ifndef CLIPPER_H
|
||||
#define CLIPPER_H
|
||||
|
||||
#include <cstdlib>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "clipper2/clipper.core.h"
|
||||
#include "clipper2/clipper.engine.h"
|
||||
|
||||
namespace Clipper2Lib {
|
||||
|
||||
inline Paths64 BooleanOp(ClipType cliptype, FillRule fillrule,
|
||||
const Paths64& subjects, const Paths64& clips)
|
||||
{
|
||||
Paths64 result;
|
||||
Clipper64 clipper;
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.AddClip(clips);
|
||||
clipper.Execute(cliptype, fillrule, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void BooleanOp(ClipType cliptype, FillRule fillrule,
|
||||
const Paths64& subjects, const Paths64& clips, PolyTree64& solution)
|
||||
{
|
||||
Paths64 sol_open;
|
||||
Clipper64 clipper;
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.AddClip(clips);
|
||||
clipper.Execute(cliptype, fillrule, solution, sol_open);
|
||||
}
|
||||
|
||||
inline PathsD BooleanOp(ClipType cliptype, FillRule fillrule,
|
||||
const PathsD& subjects, const PathsD& clips, int precision = 2)
|
||||
{
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
PathsD result;
|
||||
if (error_code) return result;
|
||||
ClipperD clipper(precision);
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.AddClip(clips);
|
||||
clipper.Execute(cliptype, fillrule, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline void BooleanOp(ClipType cliptype, FillRule fillrule,
|
||||
const PathsD& subjects, const PathsD& clips,
|
||||
PolyTreeD& polytree, int precision = 2)
|
||||
{
|
||||
polytree.Clear();
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
if (error_code) return;
|
||||
ClipperD clipper(precision);
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.AddClip(clips);
|
||||
clipper.Execute(cliptype, fillrule, polytree);
|
||||
}
|
||||
|
||||
inline Paths64 Intersect(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
|
||||
{
|
||||
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips);
|
||||
}
|
||||
|
||||
inline PathsD Intersect(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
|
||||
{
|
||||
return BooleanOp(ClipType::Intersection, fillrule, subjects, clips, decimal_prec);
|
||||
}
|
||||
|
||||
inline Paths64 Union(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
|
||||
{
|
||||
return BooleanOp(ClipType::Union, fillrule, subjects, clips);
|
||||
}
|
||||
|
||||
inline PathsD Union(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
|
||||
{
|
||||
return BooleanOp(ClipType::Union, fillrule, subjects, clips, decimal_prec);
|
||||
}
|
||||
|
||||
inline Paths64 Union(const Paths64& subjects, FillRule fillrule)
|
||||
{
|
||||
Paths64 result;
|
||||
Clipper64 clipper;
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.Execute(ClipType::Union, fillrule, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline PathsD Union(const PathsD& subjects, FillRule fillrule, int precision = 2)
|
||||
{
|
||||
PathsD result;
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
if (error_code) return result;
|
||||
ClipperD clipper(precision);
|
||||
clipper.AddSubject(subjects);
|
||||
clipper.Execute(ClipType::Union, fillrule, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline Paths64 Difference(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
|
||||
{
|
||||
return BooleanOp(ClipType::Difference, fillrule, subjects, clips);
|
||||
}
|
||||
|
||||
inline PathsD Difference(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
|
||||
{
|
||||
return BooleanOp(ClipType::Difference, fillrule, subjects, clips, decimal_prec);
|
||||
}
|
||||
|
||||
inline Paths64 Xor(const Paths64& subjects, const Paths64& clips, FillRule fillrule)
|
||||
{
|
||||
return BooleanOp(ClipType::Xor, fillrule, subjects, clips);
|
||||
}
|
||||
|
||||
inline PathsD Xor(const PathsD& subjects, const PathsD& clips, FillRule fillrule, int decimal_prec = 2)
|
||||
{
|
||||
return BooleanOp(ClipType::Xor, fillrule, subjects, clips, decimal_prec);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> TranslatePath(const Path<T>& path, T dx, T dy)
|
||||
{
|
||||
Path<T> result;
|
||||
result.reserve(path.size());
|
||||
std::transform(path.begin(), path.end(), back_inserter(result),
|
||||
[dx, dy](const auto& pt) { return Point<T>(pt.x + dx, pt.y +dy); });
|
||||
return result;
|
||||
}
|
||||
|
||||
inline Path64 TranslatePath(const Path64& path, int64_t dx, int64_t dy)
|
||||
{
|
||||
return TranslatePath<int64_t>(path, dx, dy);
|
||||
}
|
||||
|
||||
inline PathD TranslatePath(const PathD& path, double dx, double dy)
|
||||
{
|
||||
return TranslatePath<double>(path, dx, dy);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Paths<T> TranslatePaths(const Paths<T>& paths, T dx, T dy)
|
||||
{
|
||||
Paths<T> result;
|
||||
result.reserve(paths.size());
|
||||
std::transform(paths.begin(), paths.end(), back_inserter(result),
|
||||
[dx, dy](const auto& path) { return TranslatePath(path, dx, dy); });
|
||||
return result;
|
||||
}
|
||||
|
||||
inline Paths64 TranslatePaths(const Paths64& paths, int64_t dx, int64_t dy)
|
||||
{
|
||||
return TranslatePaths<int64_t>(paths, dx, dy);
|
||||
}
|
||||
|
||||
inline PathsD TranslatePaths(const PathsD& paths, double dx, double dy)
|
||||
{
|
||||
return TranslatePaths<double>(paths, dx, dy);
|
||||
}
|
||||
|
||||
namespace details
|
||||
{
|
||||
|
||||
inline void PolyPathToPaths64(const PolyPath64& polypath, Paths64& paths)
|
||||
{
|
||||
paths.push_back(polypath.Polygon());
|
||||
for (const auto& child : polypath)
|
||||
PolyPathToPaths64(*child, paths);
|
||||
}
|
||||
|
||||
inline void PolyPathToPathsD(const PolyPathD& polypath, PathsD& paths)
|
||||
{
|
||||
paths.push_back(polypath.Polygon());
|
||||
for (const auto& child : polypath)
|
||||
PolyPathToPathsD(*child, paths);
|
||||
}
|
||||
|
||||
inline bool PolyPath64ContainsChildren(const PolyPath64& pp)
|
||||
{
|
||||
for (const auto& child : pp)
|
||||
{
|
||||
// return false if this child isn't fully contained by its parent
|
||||
|
||||
// checking for a single vertex outside is a bit too crude since
|
||||
// it doesn't account for rounding errors. It's better to check
|
||||
// for consecutive vertices found outside the parent's polygon.
|
||||
|
||||
int outsideCnt = 0;
|
||||
for (const Point64& pt : child->Polygon())
|
||||
{
|
||||
PointInPolygonResult result = PointInPolygon(pt, pp.Polygon());
|
||||
if (result == PointInPolygonResult::IsInside) --outsideCnt;
|
||||
else if (result == PointInPolygonResult::IsOutside) ++outsideCnt;
|
||||
if (outsideCnt > 1) return false;
|
||||
else if (outsideCnt < -1) break;
|
||||
}
|
||||
|
||||
// now check any nested children too
|
||||
if (child->Count() > 0 && !PolyPath64ContainsChildren(*child))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void OutlinePolyPath(std::ostream& os,
|
||||
size_t idx, bool isHole, size_t count, const std::string& preamble)
|
||||
{
|
||||
std::string plural = (count == 1) ? "." : "s.";
|
||||
if (isHole)
|
||||
os << preamble << "+- Hole (" << idx << ") contains " << count <<
|
||||
" nested polygon" << plural << std::endl;
|
||||
else
|
||||
os << preamble << "+- Polygon (" << idx << ") contains " << count <<
|
||||
" hole" << plural << std::endl;
|
||||
}
|
||||
|
||||
static void OutlinePolyPath64(std::ostream& os, const PolyPath64& pp,
|
||||
size_t idx, std::string preamble)
|
||||
{
|
||||
OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble);
|
||||
for (size_t i = 0; i < pp.Count(); ++i)
|
||||
if (pp.Child(i)->Count())
|
||||
details::OutlinePolyPath64(os, *pp.Child(i), i, preamble + " ");
|
||||
}
|
||||
|
||||
static void OutlinePolyPathD(std::ostream& os, const PolyPathD& pp,
|
||||
size_t idx, std::string preamble)
|
||||
{
|
||||
OutlinePolyPath(os, idx, pp.IsHole(), pp.Count(), preamble);
|
||||
for (size_t i = 0; i < pp.Count(); ++i)
|
||||
if (pp.Child(i)->Count())
|
||||
details::OutlinePolyPathD(os, *pp.Child(i), i, preamble + " ");
|
||||
}
|
||||
|
||||
template<typename T, typename U>
|
||||
inline constexpr void MakePathGeneric(const T an_array,
|
||||
size_t array_size, std::vector<U>& result)
|
||||
{
|
||||
result.reserve(array_size / 2);
|
||||
for (size_t i = 0; i < array_size; i +=2)
|
||||
#ifdef USINGZ
|
||||
result.push_back( U{ an_array[i], an_array[i + 1], 0} );
|
||||
#else
|
||||
result.push_back( U{ an_array[i], an_array[i + 1]} );
|
||||
#endif
|
||||
}
|
||||
|
||||
} // end details namespace
|
||||
|
||||
inline std::ostream& operator<< (std::ostream& os, const PolyTree64& pp)
|
||||
{
|
||||
std::string plural = (pp.Count() == 1) ? " polygon." : " polygons.";
|
||||
os << std::endl << "Polytree with " << pp.Count() << plural << std::endl;
|
||||
for (size_t i = 0; i < pp.Count(); ++i)
|
||||
if (pp.Child(i)->Count())
|
||||
details::OutlinePolyPath64(os, *pp.Child(i), i, " ");
|
||||
os << std::endl << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
inline std::ostream& operator<< (std::ostream& os, const PolyTreeD& pp)
|
||||
{
|
||||
std::string plural = (pp.Count() == 1) ? " polygon." : " polygons.";
|
||||
os << std::endl << "Polytree with " << pp.Count() << plural << std::endl;
|
||||
for (size_t i = 0; i < pp.Count(); ++i)
|
||||
if (pp.Child(i)->Count())
|
||||
details::OutlinePolyPathD(os, *pp.Child(i), i, " ");
|
||||
os << std::endl << std::endl;
|
||||
if (!pp.Level()) os << std::endl;
|
||||
return os;
|
||||
}
|
||||
|
||||
inline Paths64 PolyTreeToPaths64(const PolyTree64& polytree)
|
||||
{
|
||||
Paths64 result;
|
||||
for (const auto& child : polytree)
|
||||
details::PolyPathToPaths64(*child, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline PathsD PolyTreeToPathsD(const PolyTreeD& polytree)
|
||||
{
|
||||
PathsD result;
|
||||
for (const auto& child : polytree)
|
||||
details::PolyPathToPathsD(*child, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline bool CheckPolytreeFullyContainsChildren(const PolyTree64& polytree)
|
||||
{
|
||||
for (const auto& child : polytree)
|
||||
if (child->Count() > 0 &&
|
||||
!details::PolyPath64ContainsChildren(*child))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T,
|
||||
typename std::enable_if<
|
||||
std::is_integral<T>::value &&
|
||||
!std::is_same<char, T>::value, bool
|
||||
>::type = true>
|
||||
inline Path64 MakePath(const std::vector<T>& list)
|
||||
{
|
||||
const auto size = list.size() - list.size() % 2;
|
||||
if (list.size() != size)
|
||||
DoError(non_pair_error_i); // non-fatal without exception handling
|
||||
Path64 result;
|
||||
details::MakePathGeneric(list, size, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T, std::size_t N,
|
||||
typename std::enable_if<
|
||||
std::is_integral<T>::value &&
|
||||
!std::is_same<char, T>::value, bool
|
||||
>::type = true>
|
||||
inline Path64 MakePath(const T(&list)[N])
|
||||
{
|
||||
// Make the compiler error on unpaired value (i.e. no runtime effects).
|
||||
static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
|
||||
Path64 result;
|
||||
details::MakePathGeneric(list, N, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T,
|
||||
typename std::enable_if<
|
||||
std::is_arithmetic<T>::value &&
|
||||
!std::is_same<char, T>::value, bool
|
||||
>::type = true>
|
||||
inline PathD MakePathD(const std::vector<T>& list)
|
||||
{
|
||||
const auto size = list.size() - list.size() % 2;
|
||||
if (list.size() != size)
|
||||
DoError(non_pair_error_i); // non-fatal without exception handling
|
||||
PathD result;
|
||||
details::MakePathGeneric(list, size, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T, std::size_t N,
|
||||
typename std::enable_if<
|
||||
std::is_arithmetic<T>::value &&
|
||||
!std::is_same<char, T>::value, bool
|
||||
>::type = true>
|
||||
inline PathD MakePathD(const T(&list)[N])
|
||||
{
|
||||
// Make the compiler error on unpaired value (i.e. no runtime effects).
|
||||
static_assert(N % 2 == 0, "MakePath requires an even number of arguments");
|
||||
PathD result;
|
||||
details::MakePathGeneric(list, N, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef USINGZ
|
||||
template<typename T2, std::size_t N>
|
||||
inline Path64 MakePathZ(const T2(&list)[N])
|
||||
{
|
||||
static_assert(N % 3 == 0 && std::numeric_limits<T2>::is_integer,
|
||||
"MakePathZ requires integer values in multiples of 3");
|
||||
std::size_t size = N / 3;
|
||||
Path64 result(size);
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
result[i] = Point64(list[i * 3],
|
||||
list[i * 3 + 1], list[i * 3 + 2]);
|
||||
return result;
|
||||
}
|
||||
|
||||
template<typename T2, std::size_t N>
|
||||
inline PathD MakePathZD(const T2(&list)[N])
|
||||
{
|
||||
static_assert(N % 3 == 0,
|
||||
"MakePathZD requires values in multiples of 3");
|
||||
std::size_t size = N / 3;
|
||||
PathD result(size);
|
||||
if constexpr (std::numeric_limits<T2>::is_integer)
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
result[i] = PointD(list[i * 3],
|
||||
list[i * 3 + 1], list[i * 3 + 2]);
|
||||
else
|
||||
for (size_t i = 0; i < size; ++i)
|
||||
result[i] = PointD(list[i * 3], list[i * 3 + 1],
|
||||
static_cast<int64_t>(list[i * 3 + 2]));
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
inline Path64 TrimCollinear(const Path64& p, bool is_open_path = false)
|
||||
{
|
||||
size_t len = p.size();
|
||||
if (len < 3)
|
||||
{
|
||||
if (!is_open_path || len < 2 || p[0] == p[1]) return Path64();
|
||||
else return p;
|
||||
}
|
||||
|
||||
Path64 dst;
|
||||
dst.reserve(len);
|
||||
Path64::const_iterator srcIt = p.cbegin(), prevIt, stop = p.cend() - 1;
|
||||
|
||||
if (!is_open_path)
|
||||
{
|
||||
while (srcIt != stop && IsCollinear(*stop, *srcIt, *(srcIt + 1)))
|
||||
++srcIt;
|
||||
while (srcIt != stop && IsCollinear(*(stop - 1), *stop, *srcIt))
|
||||
--stop;
|
||||
if (srcIt == stop) return Path64();
|
||||
}
|
||||
|
||||
prevIt = srcIt++;
|
||||
dst.push_back(*prevIt);
|
||||
for (; srcIt != stop; ++srcIt)
|
||||
{
|
||||
if (!IsCollinear(*prevIt, *srcIt, *(srcIt + 1)))
|
||||
{
|
||||
prevIt = srcIt;
|
||||
dst.push_back(*prevIt);
|
||||
}
|
||||
}
|
||||
|
||||
if (is_open_path)
|
||||
dst.push_back(*srcIt);
|
||||
else if (!IsCollinear(*prevIt, *stop, dst[0]))
|
||||
dst.push_back(*stop);
|
||||
else
|
||||
{
|
||||
while (dst.size() > 2 &&
|
||||
IsCollinear(dst[dst.size() - 1], dst[dst.size() - 2], dst[0]))
|
||||
dst.pop_back();
|
||||
if (dst.size() < 3) return Path64();
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
|
||||
inline PathD TrimCollinear(const PathD& path, int precision, bool is_open_path = false)
|
||||
{
|
||||
int error_code = 0;
|
||||
CheckPrecisionRange(precision, error_code);
|
||||
if (error_code) return PathD();
|
||||
const double scale = std::pow(10, precision);
|
||||
Path64 p = ScalePath<int64_t, double>(path, scale, error_code);
|
||||
if (error_code) return PathD();
|
||||
p = TrimCollinear(p, is_open_path);
|
||||
return ScalePath<double, int64_t>(p, 1/scale, error_code);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline double Distance(const Point<T> pt1, const Point<T> pt2)
|
||||
{
|
||||
return std::sqrt(DistanceSqr(pt1, pt2));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline double Length(const Path<T>& path, bool is_closed_path = false)
|
||||
{
|
||||
double result = 0.0;
|
||||
if (path.size() < 2) return result;
|
||||
auto it = path.cbegin(), stop = path.end() - 1;
|
||||
for (; it != stop; ++it)
|
||||
result += Distance(*it, *(it + 1));
|
||||
if (is_closed_path)
|
||||
result += Distance(*stop, *path.cbegin());
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
inline bool NearCollinear(const Point<T>& pt1, const Point<T>& pt2, const Point<T>& pt3, double sin_sqrd_min_angle_rads)
|
||||
{
|
||||
double cp = std::abs(CrossProduct(pt1, pt2, pt3));
|
||||
return (cp * cp) / (DistanceSqr(pt1, pt2) * DistanceSqr(pt2, pt3)) < sin_sqrd_min_angle_rads;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> Ellipse(const Rect<T>& rect, size_t steps = 0)
|
||||
{
|
||||
return Ellipse(rect.MidPoint(),
|
||||
static_cast<double>(rect.Width()) *0.5,
|
||||
static_cast<double>(rect.Height()) * 0.5, steps);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> Ellipse(const Point<T>& center,
|
||||
double radiusX, double radiusY = 0, size_t steps = 0)
|
||||
{
|
||||
if (radiusX <= 0) return Path<T>();
|
||||
if (radiusY <= 0) radiusY = radiusX;
|
||||
if (steps <= 2)
|
||||
steps = static_cast<size_t>(PI * sqrt((radiusX + radiusY) / 2));
|
||||
|
||||
double si = std::sin(2 * PI / steps);
|
||||
double co = std::cos(2 * PI / steps);
|
||||
double dx = co, dy = si;
|
||||
Path<T> result;
|
||||
result.reserve(steps);
|
||||
result.push_back(Point<T>(center.x + radiusX, static_cast<double>(center.y)));
|
||||
for (size_t i = 1; i < steps; ++i)
|
||||
{
|
||||
result.push_back(Point<T>(center.x + radiusX * dx, center.y + radiusY * dy));
|
||||
double x = dx * co - dy * si;
|
||||
dy = dy * co + dx * si;
|
||||
dx = x;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
inline size_t GetNext(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
++current;
|
||||
while (current <= high && flags[current]) ++current;
|
||||
if (current <= high) return current;
|
||||
current = 0;
|
||||
while (flags[current]) ++current;
|
||||
return current;
|
||||
}
|
||||
|
||||
inline size_t GetPrior(size_t current, size_t high,
|
||||
const std::vector<bool>& flags)
|
||||
{
|
||||
if (current == 0) current = high;
|
||||
else --current;
|
||||
while (current > 0 && flags[current]) --current;
|
||||
if (!flags[current]) return current;
|
||||
current = high;
|
||||
while (flags[current]) --current;
|
||||
return current;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> SimplifyPath(const Path<T> &path,
|
||||
double epsilon, bool isClosedPath = true)
|
||||
{
|
||||
const size_t len = path.size(), high = len -1;
|
||||
const double epsSqr = Sqr(epsilon);
|
||||
if (len < 4) return Path<T>(path);
|
||||
|
||||
std::vector<bool> flags(len);
|
||||
std::vector<double> distSqr(len);
|
||||
size_t prior = high, curr = 0, start, next, prior2;
|
||||
if (isClosedPath)
|
||||
{
|
||||
distSqr[0] = PerpendicDistFromLineSqrd(path[0], path[high], path[1]);
|
||||
distSqr[high] = PerpendicDistFromLineSqrd(path[high], path[0], path[high - 1]);
|
||||
}
|
||||
else
|
||||
{
|
||||
distSqr[0] = MAX_DBL;
|
||||
distSqr[high] = MAX_DBL;
|
||||
}
|
||||
for (size_t i = 1; i < high; ++i)
|
||||
distSqr[i] = PerpendicDistFromLineSqrd(path[i], path[i - 1], path[i + 1]);
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (distSqr[curr] > epsSqr)
|
||||
{
|
||||
start = curr;
|
||||
do
|
||||
{
|
||||
curr = GetNext(curr, high, flags);
|
||||
} while (curr != start && distSqr[curr] > epsSqr);
|
||||
if (curr == start) break;
|
||||
}
|
||||
|
||||
prior = GetPrior(curr, high, flags);
|
||||
next = GetNext(curr, high, flags);
|
||||
if (next == prior) break;
|
||||
|
||||
// flag for removal the smaller of adjacent 'distances'
|
||||
if (distSqr[next] < distSqr[curr])
|
||||
{
|
||||
prior2 = prior;
|
||||
prior = curr;
|
||||
curr = next;
|
||||
next = GetNext(next, high, flags);
|
||||
}
|
||||
else
|
||||
prior2 = GetPrior(prior, high, flags);
|
||||
|
||||
flags[curr] = true;
|
||||
curr = next;
|
||||
next = GetNext(next, high, flags);
|
||||
|
||||
if (isClosedPath || ((curr != high) && (curr != 0)))
|
||||
distSqr[curr] = PerpendicDistFromLineSqrd(path[curr], path[prior], path[next]);
|
||||
if (isClosedPath || ((prior != 0) && (prior != high)))
|
||||
distSqr[prior] = PerpendicDistFromLineSqrd(path[prior], path[prior2], path[curr]);
|
||||
}
|
||||
Path<T> result;
|
||||
result.reserve(len);
|
||||
for (typename Path<T>::size_type i = 0; i < len; ++i)
|
||||
if (!flags[i]) result.push_back(path[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Paths<T> SimplifyPaths(const Paths<T> &paths,
|
||||
double epsilon, bool isClosedPath = true)
|
||||
{
|
||||
Paths<T> result;
|
||||
result.reserve(paths.size());
|
||||
for (const auto& path : paths)
|
||||
result.push_back(SimplifyPath(path, epsilon, isClosedPath));
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void RDP(const Path<T> path, std::size_t begin,
|
||||
std::size_t end, double epsSqrd, std::vector<bool>& flags)
|
||||
{
|
||||
typename Path<T>::size_type idx = 0;
|
||||
double max_d = 0;
|
||||
while (end > begin && path[begin] == path[end]) flags[end--] = false;
|
||||
for (typename Path<T>::size_type i = begin + 1; i < end; ++i)
|
||||
{
|
||||
// PerpendicDistFromLineSqrd - avoids expensive Sqrt()
|
||||
double d = PerpendicDistFromLineSqrd(path[i], path[begin], path[end]);
|
||||
if (d <= max_d) continue;
|
||||
max_d = d;
|
||||
idx = i;
|
||||
}
|
||||
if (max_d <= epsSqrd) return;
|
||||
flags[idx] = true;
|
||||
if (idx > begin + 1) RDP(path, begin, idx, epsSqrd, flags);
|
||||
if (idx < end - 1) RDP(path, idx, end, epsSqrd, flags);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Path<T> RamerDouglasPeucker(const Path<T>& path, double epsilon)
|
||||
{
|
||||
const typename Path<T>::size_type len = path.size();
|
||||
if (len < 5) return Path<T>(path);
|
||||
std::vector<bool> flags(len);
|
||||
flags[0] = true;
|
||||
flags[len - 1] = true;
|
||||
RDP(path, 0, len - 1, Sqr(epsilon), flags);
|
||||
Path<T> result;
|
||||
result.reserve(len);
|
||||
for (typename Path<T>::size_type i = 0; i < len; ++i)
|
||||
if (flags[i])
|
||||
result.push_back(path[i]);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline Paths<T> RamerDouglasPeucker(const Paths<T>& paths, double epsilon)
|
||||
{
|
||||
Paths<T> result;
|
||||
result.reserve(paths.size());
|
||||
std::transform(paths.begin(), paths.end(), back_inserter(result),
|
||||
[epsilon](const auto& path)
|
||||
{ return RamerDouglasPeucker<T>(path, epsilon); });
|
||||
return result;
|
||||
}
|
||||
|
||||
} // end Clipper2Lib namespace
|
||||
|
||||
#endif // CLIPPER_H
|
||||
@@ -0,0 +1,6 @@
|
||||
#ifndef CLIPPER_VERSION_H
|
||||
#define CLIPPER_VERSION_H
|
||||
|
||||
constexpr auto CLIPPER2_VERSION = "1.4.0";
|
||||
|
||||
#endif // CLIPPER_VERSION_H
|
||||
File diff suppressed because it is too large
Load Diff
+19
-10
@@ -28,6 +28,7 @@
|
||||
int minzoom = 0;
|
||||
int maxzoom = 32;
|
||||
bool force = false;
|
||||
std::set<std::string> include_attr;
|
||||
|
||||
bool progress_time() {
|
||||
return false;
|
||||
@@ -217,7 +218,7 @@ void handle(std::string message, int z, unsigned x, unsigned y, std::set<std::st
|
||||
} else if (coordinate_mode == 2) { // integer
|
||||
scale = 1;
|
||||
}
|
||||
layer_to_geojson(layer, z, x, y, !pipeline, pipeline, pipeline, false, 0, 0, 0, !force, state, scale);
|
||||
layer_to_geojson(layer, z, x, y, !pipeline, pipeline, pipeline, false, 0, 0, 0, !force, state, scale, include_attr);
|
||||
|
||||
if (!pipeline) {
|
||||
if (true) {
|
||||
@@ -246,23 +247,26 @@ void decode(char *fname, int z, unsigned x, unsigned y, std::set<std::string> co
|
||||
if (fd >= 0) {
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == 0) {
|
||||
if (st.st_size < 50 * 1024 * 1024) {
|
||||
char *map = (char *) mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (map != NULL && map != MAP_FAILED) {
|
||||
if (strcmp(map, "SQLite format 3") != 0 && strncmp(map, "PMTiles", 7) != 0) {
|
||||
if (z >= 0) {
|
||||
char *map = (char *) mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if (map != NULL && map != MAP_FAILED) {
|
||||
if (strcmp(map, "SQLite format 3") != 0 && strncmp(map, "PMTiles", 7) != 0) {
|
||||
if (z >= 0) {
|
||||
if (st.st_size > 250 * 1024 * 1024) {
|
||||
fprintf(stderr, "%s: unrealistically large single-tile size %zu\n", fname, (size_t) st.st_size);
|
||||
exit(EXIT_MEMORY);
|
||||
} else {
|
||||
std::string s = std::string(map, st.st_size);
|
||||
handle(s, z, x, y, to_decode, pipeline, stats, state, coordinate_mode);
|
||||
munmap(map, st.st_size);
|
||||
return;
|
||||
} else {
|
||||
fprintf(stderr, "Must specify zoom/x/y to decode a single pbf file\n");
|
||||
exit(EXIT_ARGS);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Must specify zoom/x/y to decode a single pbf file\n");
|
||||
exit(EXIT_ARGS);
|
||||
}
|
||||
}
|
||||
munmap(map, st.st_size);
|
||||
}
|
||||
munmap(map, st.st_size);
|
||||
} else {
|
||||
perror("fstat");
|
||||
}
|
||||
@@ -571,6 +575,7 @@ int main(int argc, char **argv) {
|
||||
{"stats", no_argument, 0, 'S'},
|
||||
{"force", no_argument, 0, 'f'},
|
||||
{"exclude-metadata-row", required_argument, 0, 'x'},
|
||||
{"include", required_argument, 0, 'y'},
|
||||
{0, 0, 0, 0},
|
||||
};
|
||||
|
||||
@@ -630,6 +635,10 @@ int main(int argc, char **argv) {
|
||||
exclude_meta.insert(optarg);
|
||||
break;
|
||||
|
||||
case 'y':
|
||||
include_attr.insert(optarg);
|
||||
break;
|
||||
|
||||
default:
|
||||
usage(argv);
|
||||
}
|
||||
|
||||
+3
-2
@@ -146,8 +146,9 @@ void readFeature(const FlatGeobuf::Feature *feature, long long feature_sequence_
|
||||
sf.geometry = dv;
|
||||
sf.t = drawvec_type;
|
||||
|
||||
std::vector<std::string> full_keys;
|
||||
std::vector<std::shared_ptr<std::string>> full_keys;
|
||||
std::vector<serial_val> full_values;
|
||||
key_pool key_pool;
|
||||
|
||||
// assume tabular schema with columns in header
|
||||
size_t p_pos = 0;
|
||||
@@ -243,7 +244,7 @@ void readFeature(const FlatGeobuf::Feature *feature, long long feature_sequence_
|
||||
fprintf(stderr, "flatgeobuf has unsupported column type %u\n", (unsigned int)col_type);
|
||||
exit(EXIT_IMPOSSIBLE);
|
||||
}
|
||||
full_keys.push_back(h_column_names[col_idx]);
|
||||
full_keys.push_back(key_pool.pool(h_column_names[col_idx]));
|
||||
full_values.push_back(sv);
|
||||
}
|
||||
|
||||
|
||||
+5
-4
@@ -270,14 +270,14 @@ std::vector<drawvec_type> readGeometry(protozero::pbf_reader &pbf, size_t dim, d
|
||||
return ret;
|
||||
}
|
||||
|
||||
void readFeature(protozero::pbf_reader &pbf, size_t dim, double e, std::vector<std::string> &keys, struct serialization_state *sst, int layer, std::string layername) {
|
||||
void readFeature(protozero::pbf_reader &pbf, size_t dim, double e, std::vector<std::string> &keys, struct serialization_state *sst, int layer, std::string layername, key_pool &key_pool) {
|
||||
std::vector<drawvec_type> dv;
|
||||
long long id = 0;
|
||||
bool has_id = false;
|
||||
std::vector<serial_val> values;
|
||||
std::map<std::string, serial_val> other;
|
||||
|
||||
std::vector<std::string> full_keys;
|
||||
std::vector<std::shared_ptr<std::string>> full_keys;
|
||||
std::vector<serial_val> full_values;
|
||||
|
||||
while (pbf.next()) {
|
||||
@@ -338,7 +338,7 @@ void readFeature(protozero::pbf_reader &pbf, size_t dim, double e, std::vector<s
|
||||
exit(EXIT_IMPOSSIBLE);
|
||||
}
|
||||
|
||||
full_keys.push_back(keys[properties[i]]);
|
||||
full_keys.push_back(key_pool.pool(keys[properties[i]]));
|
||||
full_values.push_back(values[properties[i + 1]]);
|
||||
}
|
||||
|
||||
@@ -434,10 +434,11 @@ struct queue_run_arg {
|
||||
|
||||
void *run_parse_feature(void *v) {
|
||||
struct queue_run_arg *qra = (struct queue_run_arg *) v;
|
||||
key_pool key_pool;
|
||||
|
||||
for (size_t i = qra->start; i < qra->end; i++) {
|
||||
struct queued_feature &qf = feature_queue[i];
|
||||
readFeature(qf.pbf, qf.dim, qf.e, *qf.keys, &(*qf.sst)[qra->segment], qf.layer, qf.layername);
|
||||
readFeature(qf.pbf, qf.dim, qf.e, *qf.keys, &(*qf.sst)[qra->segment], qf.layer, qf.layername, key_pool);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
|
||||
+3
-2
@@ -58,6 +58,7 @@ void parse_geocsv(std::vector<struct serialization_state> &sst, std::string fnam
|
||||
}
|
||||
|
||||
size_t seq = 0;
|
||||
key_pool key_pool;
|
||||
while ((s = csv_getline(f)).size() > 0) {
|
||||
std::string err = check_utf8(s);
|
||||
if (err != "") {
|
||||
@@ -89,7 +90,7 @@ void parse_geocsv(std::vector<struct serialization_state> &sst, std::string fnam
|
||||
drawvec dv;
|
||||
dv.push_back(draw(VT_MOVETO, x, y));
|
||||
|
||||
std::vector<std::string> full_keys;
|
||||
std::vector<std::shared_ptr<std::string>> full_keys;
|
||||
std::vector<serial_val> full_values;
|
||||
|
||||
for (size_t i = 0; i < line.size(); i++) {
|
||||
@@ -107,7 +108,7 @@ void parse_geocsv(std::vector<struct serialization_state> &sst, std::string fnam
|
||||
}
|
||||
sv.s = line[i];
|
||||
|
||||
full_keys.push_back(header[i]);
|
||||
full_keys.push_back(key_pool.pool(header[i]));
|
||||
full_values.push_back(sv);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-5
@@ -182,23 +182,24 @@ int serialize_geojson_feature(struct serialization_state *sst, json_object *geom
|
||||
nprop = properties->value.object.length;
|
||||
}
|
||||
|
||||
std::vector<std::string> keys;
|
||||
std::vector<std::shared_ptr<std::string>> full_keys;
|
||||
std::vector<serial_val> values;
|
||||
|
||||
keys.reserve(nprop);
|
||||
full_keys.reserve(nprop);
|
||||
values.reserve(nprop);
|
||||
key_pool key_pool;
|
||||
|
||||
for (size_t i = 0; i < nprop; i++) {
|
||||
if (properties->value.object.keys[i]->type == JSON_STRING) {
|
||||
serial_val sv = stringify_value(properties->value.object.values[i], sst->fname, sst->line, feature);
|
||||
|
||||
keys.emplace_back(properties->value.object.keys[i]->value.string.string);
|
||||
full_keys.emplace_back(key_pool.pool(properties->value.object.keys[i]->value.string.string));
|
||||
values.push_back(std::move(sv));
|
||||
}
|
||||
}
|
||||
|
||||
drawvec dv;
|
||||
parse_geometry(t, coordinates, dv, VT_MOVETO, sst->fname, sst->line, feature);
|
||||
parse_coordinates(t, coordinates, dv, VT_MOVETO, sst->fname, sst->line, feature);
|
||||
|
||||
serial_feature sf;
|
||||
sf.layer = layer;
|
||||
@@ -211,7 +212,7 @@ int serialize_geojson_feature(struct serialization_state *sst, json_object *geom
|
||||
sf.geometry = dv;
|
||||
sf.feature_minzoom = 0; // Will be filled in during index merging
|
||||
sf.seq = *(sst->layer_seq);
|
||||
sf.full_keys = std::move(keys);
|
||||
sf.full_keys = std::move(full_keys);
|
||||
sf.full_values = std::move(values);
|
||||
|
||||
return serialize_feature(sst, sf, tippecanoe_layername);
|
||||
|
||||
+25
-2
@@ -99,8 +99,13 @@ drawvec clip_lines(drawvec &geom, long long x1, long long y1, long long x2, long
|
||||
drawvec clip_point(drawvec &geom, long long x1, long long y1, long long x2, long long y2);
|
||||
void visvalingam(drawvec &ls, size_t start, size_t end, double threshold, size_t retain);
|
||||
int pnpoly(const drawvec &vert, size_t start, size_t nvert, long long testx, long long testy);
|
||||
bool pnpoly_mp(drawvec const &geom, long long x, long long y);
|
||||
double distance_from_line(long long point_x, long long point_y, long long segA_x, long long segA_y, long long segB_x, long long segB_y);
|
||||
|
||||
drawvec clip_poly_poly(drawvec const &geom, drawvec const &bounds);
|
||||
drawvec clip_lines_poly(drawvec const &geom, drawvec const &bounds);
|
||||
drawvec clip_point_poly(drawvec const &geom, drawvec const &bounds);
|
||||
|
||||
struct input_tile {
|
||||
std::string tile;
|
||||
int z;
|
||||
@@ -115,6 +120,20 @@ struct source_tile {
|
||||
int y;
|
||||
};
|
||||
|
||||
struct clipbbox {
|
||||
double lon1;
|
||||
double lat1;
|
||||
double lon2;
|
||||
double lat2;
|
||||
|
||||
long long minx;
|
||||
long long miny;
|
||||
long long maxx;
|
||||
long long maxy;
|
||||
|
||||
drawvec dv; // empty, or arbitrary clipping polygon
|
||||
};
|
||||
|
||||
std::string overzoom(std::vector<source_tile> const &tiles, int nz, int nx, int ny,
|
||||
int detail, int buffer,
|
||||
std::set<std::string> const &keep,
|
||||
@@ -127,7 +146,8 @@ std::string overzoom(std::vector<source_tile> const &tiles, int nz, int nx, int
|
||||
std::vector<std::string> const &unidecode_data, double simplification,
|
||||
double tiny_polygon_size,
|
||||
std::vector<mvt_layer> const &bins, std::string const &bin_by_id_list,
|
||||
std::string const &accumulate_numeric);
|
||||
std::string const &accumulate_numeric, size_t feature_limit,
|
||||
std::vector<clipbbox> const &clipbboxes);
|
||||
|
||||
std::string overzoom(std::vector<input_tile> const &tiles, int nz, int nx, int ny,
|
||||
int detail, int buffer,
|
||||
@@ -141,11 +161,14 @@ std::string overzoom(std::vector<input_tile> const &tiles, int nz, int nx, int n
|
||||
std::vector<std::string> const &unidecode_data, double simplification,
|
||||
double tiny_polygon_size,
|
||||
std::vector<mvt_layer> const &bins, std::string const &bin_by_id_list,
|
||||
std::string const &accumulate_numeric);
|
||||
std::string const &accumulate_numeric, size_t feature_limit,
|
||||
std::vector<clipbbox> const &clipbboxes);
|
||||
|
||||
draw center_of_mass_mp(const drawvec &dv);
|
||||
|
||||
void get_quadkey_bounds(long long xmin, long long ymin, long long xmax, long long ymax,
|
||||
unsigned long long *start, unsigned long long *end);
|
||||
|
||||
clipbbox parse_clip_poly(std::string arg);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1241,6 +1241,10 @@ int vertexcmp(const void *void1, const void *void2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
double round_droprate(double r) {
|
||||
return std::round(r * 100000.0) / 100000.0;
|
||||
}
|
||||
|
||||
std::pair<int, metadata> read_input(std::vector<source> &sources, char *fname, int maxzoom, int minzoom, int basezoom, double basezoom_marker_width, sqlite3 *outdb, const char *outdir, std::set<std::string> *exclude, std::set<std::string> *include, int exclude_all, json_object *filter, double droprate, int buffer, const char *tmpdir, double gamma, int read_parallel, int forcetable, const char *attribution, bool uses_gamma, long long *file_bbox, long long *file_bbox1, long long *file_bbox2, const char *prefilter, const char *postfilter, const char *description, bool guess_maxzoom, bool guess_cluster_maxzoom, std::unordered_map<std::string, int> const *attribute_types, const char *pgm, std::unordered_map<std::string, attribute_op> const *attribute_accum, std::map<std::string, std::string> const &attribute_descriptions, std::string const &commandline, int minimum_maxzoom) {
|
||||
int ret = EXIT_SUCCESS;
|
||||
|
||||
@@ -2295,6 +2299,7 @@ std::pair<int, metadata> read_input(std::vector<source> &sources, char *fname, i
|
||||
double mean = 0;
|
||||
size_t count = 0;
|
||||
double m2 = 0;
|
||||
size_t dupes = 0;
|
||||
|
||||
long long progress = -1;
|
||||
long long ip;
|
||||
@@ -2330,6 +2335,8 @@ std::pair<int, metadata> read_input(std::vector<source> &sources, char *fname, i
|
||||
mean += delta / count;
|
||||
double delta2 = newValue - mean;
|
||||
m2 += delta * delta2;
|
||||
} else {
|
||||
dupes++;
|
||||
}
|
||||
|
||||
long long nprogress = 100 * ip / indices;
|
||||
@@ -2376,13 +2383,6 @@ std::pair<int, metadata> read_input(std::vector<source> &sources, char *fname, i
|
||||
if (maxzoom < 0) {
|
||||
maxzoom = 0;
|
||||
}
|
||||
if (maxzoom > 32 - full_detail) {
|
||||
maxzoom = 32 - full_detail;
|
||||
}
|
||||
if (maxzoom > 33 - low_detail) { // that is, maxzoom - 1 > 32 - low_detail
|
||||
maxzoom = 33 - low_detail;
|
||||
}
|
||||
|
||||
if (!quiet) {
|
||||
fprintf(stderr,
|
||||
"Choosing a maxzoom of -z%d for features typically %d feet (%d meters) apart, ",
|
||||
@@ -2413,7 +2413,7 @@ std::pair<int, metadata> read_input(std::vector<source> &sources, char *fname, i
|
||||
// features is small, the drop rate should be large because the features are evenly
|
||||
// spaced, and if the standard deviation is large, the drop rate can be small because
|
||||
// the features are in clumps.
|
||||
droprate = exp(-0.7681 * log(stddev) + 1.582);
|
||||
droprate = round_droprate(exp(-0.7681 * log(stddev) + 1.582));
|
||||
|
||||
if (droprate < 0) {
|
||||
droprate = 0;
|
||||
@@ -2422,6 +2422,13 @@ std::pair<int, metadata> read_input(std::vector<source> &sources, char *fname, i
|
||||
if (!quiet) {
|
||||
fprintf(stderr, "Choosing a drop rate of %f\n", droprate);
|
||||
}
|
||||
|
||||
if (dupes != 0 && droprate != 0) {
|
||||
maxzoom += std::round(log((dupes + count) / count) / log(droprate));
|
||||
if (!quiet) {
|
||||
fprintf(stderr, "Increasing maxzoom to %d to account for %zu duplicate feature locations\n", maxzoom, dupes);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2430,16 +2437,6 @@ std::pair<int, metadata> read_input(std::vector<source> &sources, char *fname, i
|
||||
double want2 = exp(dist_sum / dist_count) / 8;
|
||||
int mz = ceil(log(360 / (.00000274 * want2)) / log(2) - full_detail);
|
||||
|
||||
if (mz < 0) {
|
||||
mz = 0;
|
||||
}
|
||||
if (mz > 32 - full_detail) {
|
||||
mz = 32 - full_detail;
|
||||
}
|
||||
if (mz > 33 - low_detail) { // that is, mz - 1 > 32 - low_detail
|
||||
mz = 33 - low_detail;
|
||||
}
|
||||
|
||||
if (mz > maxzoom || count <= 0) {
|
||||
if (!quiet) {
|
||||
fprintf(stderr, "Choosing a maxzoom of -z%d for resolution of about %d feet (%d meters) within features\n", mz, (int) exp(dist_sum / dist_count), (int) (exp(dist_sum / dist_count) / 3.28084));
|
||||
@@ -2448,6 +2445,16 @@ std::pair<int, metadata> read_input(std::vector<source> &sources, char *fname, i
|
||||
}
|
||||
}
|
||||
|
||||
if (maxzoom < 0) {
|
||||
maxzoom = 0;
|
||||
}
|
||||
if (maxzoom > 32 - full_detail) {
|
||||
maxzoom = 32 - full_detail;
|
||||
}
|
||||
if (maxzoom > 33 - low_detail) { // that is, maxzoom - 1 > 32 - low_detail
|
||||
maxzoom = 33 - low_detail;
|
||||
}
|
||||
|
||||
double total_tile_count = 0;
|
||||
for (int i = 1; i <= maxzoom; i++) {
|
||||
double tile_count = ceil(area_sum / ((1LL << (32 - i)) * (1LL << (32 - i))));
|
||||
@@ -2608,7 +2615,7 @@ std::pair<int, metadata> read_input(std::vector<source> &sources, char *fname, i
|
||||
if (maxzoom == 0) {
|
||||
droprate = 2.5;
|
||||
} else {
|
||||
droprate = exp(log((double) max[0].count / max[maxzoom].count) / (maxzoom));
|
||||
droprate = round_droprate(exp(log((double) max[0].count / max[maxzoom].count) / (maxzoom)));
|
||||
if (!quiet) {
|
||||
fprintf(stderr, "Choosing a drop rate of -r%f to get from %lld to %lld in %d zooms\n", droprate, max[maxzoom].count, max[0].count, maxzoom);
|
||||
}
|
||||
@@ -2634,7 +2641,7 @@ std::pair<int, metadata> read_input(std::vector<source> &sources, char *fname, i
|
||||
|
||||
if (max[z].count / interval >= max_features) {
|
||||
interval = (double) max[z].count / max_features;
|
||||
droprate = exp(log(interval) / (basezoom - z));
|
||||
droprate = round_droprate(exp(log(interval) / (basezoom - z)));
|
||||
interval = exp(log(droprate) * (basezoom - z));
|
||||
|
||||
if (!quiet) {
|
||||
|
||||
@@ -24,18 +24,6 @@ struct index {
|
||||
}
|
||||
};
|
||||
|
||||
struct clipbbox {
|
||||
double lon1;
|
||||
double lat1;
|
||||
double lon2;
|
||||
double lat2;
|
||||
|
||||
long long minx;
|
||||
long long miny;
|
||||
long long maxx;
|
||||
long long maxy;
|
||||
};
|
||||
|
||||
extern std::vector<clipbbox> clipbboxes;
|
||||
|
||||
void checkdisk(std::vector<struct reader> *r);
|
||||
|
||||
+1
-1
@@ -700,7 +700,7 @@ metadata make_metadata(const char *fname, int minzoom, int maxzoom, double minla
|
||||
|
||||
m.strategies_json = stringify_strategies(strategies);
|
||||
|
||||
if (isinf(droprate)) {
|
||||
if (std::isinf(droprate)) {
|
||||
droprate = LLONG_MAX;
|
||||
}
|
||||
if (basezoom != maxzoom || droprate != 2.5 || retain_points_multiplier != 1) {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
#include <optional>
|
||||
#include <memory>
|
||||
#include <cmath>
|
||||
|
||||
#include "errors.hpp"
|
||||
#include "text.hpp"
|
||||
@@ -76,6 +77,9 @@ enum mvt_value_type {
|
||||
mvt_no_such_key,
|
||||
};
|
||||
|
||||
struct mvt_value;
|
||||
double mvt_value_to_double(mvt_value const &v);
|
||||
|
||||
struct mvt_value {
|
||||
mvt_value_type type;
|
||||
std::shared_ptr<std::string> s;
|
||||
@@ -95,7 +99,11 @@ struct mvt_value {
|
||||
} numeric_value;
|
||||
|
||||
std::string get_string_value() const {
|
||||
return std::string(*s, numeric_value.string_value.off, numeric_value.string_value.len);
|
||||
if (type == mvt_string) {
|
||||
return std::string(*s, numeric_value.string_value.off, numeric_value.string_value.len);
|
||||
} else {
|
||||
return toString();
|
||||
}
|
||||
}
|
||||
|
||||
std::string_view get_string_view() const {
|
||||
@@ -126,6 +134,10 @@ struct mvt_value {
|
||||
type == mvt_sint;
|
||||
}
|
||||
|
||||
double to_double() const {
|
||||
return mvt_value_to_double(*this);
|
||||
}
|
||||
|
||||
bool operator<(const mvt_value &o) const;
|
||||
bool operator==(const mvt_value &o) const;
|
||||
std::string toString() const;
|
||||
@@ -134,6 +146,11 @@ struct mvt_value {
|
||||
this->type = mvt_double;
|
||||
this->numeric_value.double_value = 0;
|
||||
}
|
||||
|
||||
mvt_value(double v) {
|
||||
this->type = mvt_double;
|
||||
this->numeric_value.double_value = v;
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
@@ -185,6 +202,10 @@ struct mvt_layer {
|
||||
// For tracking the key-value constants already used in this layer
|
||||
std::vector<ssize_t> key_dedup = std::vector<ssize_t>(65536, -1);
|
||||
std::vector<ssize_t> value_dedup = std::vector<ssize_t>(65536, -1);
|
||||
|
||||
int detail() const {
|
||||
return std::round(std::log(extent) / std::log(2));
|
||||
}
|
||||
};
|
||||
|
||||
struct mvt_tile {
|
||||
@@ -201,7 +222,6 @@ int dezig(unsigned n);
|
||||
|
||||
mvt_value stringified_to_mvt_value(int type, const char *s, std::shared_ptr<std::string> const &tile_stringpool);
|
||||
long long mvt_value_to_long_long(mvt_value const &v);
|
||||
double mvt_value_to_double(mvt_value const &v);
|
||||
|
||||
bool is_integer(const char *s, long long *v);
|
||||
bool is_unsigned_integer(const char *s, unsigned long long *v);
|
||||
|
||||
+64
-2
@@ -10,6 +10,7 @@
|
||||
#include "attribute.hpp"
|
||||
#include "text.hpp"
|
||||
#include "read_json.hpp"
|
||||
#include "projection.hpp"
|
||||
|
||||
extern char *optarg;
|
||||
extern int optind;
|
||||
@@ -17,6 +18,8 @@ extern int optind;
|
||||
int detail = 12; // tippecanoe-style: mvt extent == 1 << detail
|
||||
int buffer = 5; // tippecanoe-style: mvt buffer == extent * buffer / 256;
|
||||
bool demultiply = false;
|
||||
bool do_compress = true;
|
||||
|
||||
std::string filter;
|
||||
bool preserve_input_order = false;
|
||||
std::unordered_map<std::string, attribute_op> attribute_accum;
|
||||
@@ -27,6 +30,7 @@ std::string accumulate_numeric;
|
||||
std::set<std::string> keep;
|
||||
std::set<std::string> exclude;
|
||||
std::vector<std::string> exclude_prefix;
|
||||
std::vector<clipbbox> clipbboxes;
|
||||
|
||||
void usage(char **argv) {
|
||||
fprintf(stderr, "Usage: %s -o newtile.pbf.gz tile.pbf.gz oz/ox/oy nz/nx/ny\n", argv[0]);
|
||||
@@ -65,6 +69,9 @@ int main(int argc, char **argv) {
|
||||
{"assign-to-bins", required_argument, 0, 'b' & 0x1F},
|
||||
{"bin-by-id-list", required_argument, 0, 'c' & 0x1F},
|
||||
{"accumulate-numeric-attributes", required_argument, 0, 'a' & 0x1F},
|
||||
{"no-tile-compression", no_argument, 0, 'd' & 0x1F},
|
||||
{"clip-bounding-box", required_argument, 0, 'k' & 0x1F},
|
||||
{"clip-polygon", required_argument, 0, 'l' & 0x1F},
|
||||
|
||||
{0, 0, 0, 0},
|
||||
};
|
||||
@@ -151,6 +158,29 @@ int main(int argc, char **argv) {
|
||||
accumulate_numeric = optarg;
|
||||
break;
|
||||
|
||||
case 'd' & 0x1F:
|
||||
do_compress = false;
|
||||
break;
|
||||
|
||||
case 'k' & 0x1F: {
|
||||
clipbbox clip;
|
||||
if (sscanf(optarg, "%lf,%lf,%lf,%lf", &clip.lon1, &clip.lat1, &clip.lon2, &clip.lat2) == 4) {
|
||||
projection->project(clip.lon1, clip.lat1, 32, &clip.minx, &clip.maxy);
|
||||
projection->project(clip.lon2, clip.lat2, 32, &clip.maxx, &clip.miny);
|
||||
clipbboxes.push_back(clip);
|
||||
} else {
|
||||
fprintf(stderr, "%s: Can't parse bounding box --clip-bounding-box=%s\n", argv[0], optarg);
|
||||
exit(EXIT_ARGS);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'l' & 0x1F: {
|
||||
clipbbox clip = parse_clip_poly(optarg);
|
||||
clipbboxes.push_back(clip);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unrecognized flag -%c\n", i);
|
||||
usage(argv);
|
||||
@@ -221,10 +251,42 @@ int main(int argc, char **argv) {
|
||||
exit(EXIT_OPEN);
|
||||
}
|
||||
|
||||
bins = parse_layers(f, nz, nx, ny, 1LL << detail, true);
|
||||
int det = detail;
|
||||
if (det < 0) {
|
||||
det = 12;
|
||||
}
|
||||
bins = parse_layers(f, nz, nx, ny, 1LL << det, true);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
// clip the clip polygons, if any, to the tile bounds,
|
||||
// to reduce their complexity
|
||||
|
||||
if (clipbboxes.size() > 0) {
|
||||
long long wx1 = (nx - buffer / 256.0) * (1LL << (32 - nz));
|
||||
long long wy1 = (ny - buffer / 256.0) * (1LL << (32 - nz));
|
||||
long long wx2 = (nx + 1 + buffer / 256.0) * (1LL << (32 - nz));
|
||||
long long wy2 = (ny + 1 + buffer / 256.0) * (1LL << (32 - nz));
|
||||
|
||||
drawvec tile_bounds;
|
||||
tile_bounds.emplace_back(VT_MOVETO, wx1, wy1);
|
||||
tile_bounds.emplace_back(VT_LINETO, wx2, wy1);
|
||||
tile_bounds.emplace_back(VT_LINETO, wx2, wy2);
|
||||
tile_bounds.emplace_back(VT_LINETO, wx1, wy2);
|
||||
tile_bounds.emplace_back(VT_LINETO, wx1, wy1);
|
||||
|
||||
for (auto &c : clipbboxes) {
|
||||
c.minx = std::max(c.minx, wx1);
|
||||
c.miny = std::max(c.miny, wy1);
|
||||
c.maxx = std::min(c.maxx, wx2);
|
||||
c.maxy = std::min(c.maxy, wy2);
|
||||
|
||||
if (c.dv.size() > 0) {
|
||||
c.dv = clip_poly_poly(c.dv, tile_bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json_object *json_filter = NULL;
|
||||
if (filter.size() > 0) {
|
||||
json_filter = parse_filter(filter.c_str());
|
||||
@@ -251,7 +313,7 @@ int main(int argc, char **argv) {
|
||||
its.push_back(std::move(t));
|
||||
}
|
||||
|
||||
std::string out = overzoom(its, nz, nx, ny, detail, buffer, keep, exclude, exclude_prefix, true, NULL, demultiply, json_filter, preserve_input_order, attribute_accum, unidecode_data, simplification, tiny_polygon_size, bins, bin_by_id_list, accumulate_numeric);
|
||||
std::string out = overzoom(its, nz, nx, ny, detail, buffer, keep, exclude, exclude_prefix, do_compress, NULL, demultiply, json_filter, preserve_input_order, attribute_accum, unidecode_data, simplification, tiny_polygon_size, bins, bin_by_id_list, accumulate_numeric, SIZE_MAX, clipbboxes);
|
||||
|
||||
FILE *f = fopen(outfile, "wb");
|
||||
if (f == NULL) {
|
||||
|
||||
+4
-4
@@ -55,7 +55,7 @@ void *run_writer(void *a) {
|
||||
|
||||
json_writer state(fp);
|
||||
for (size_t i = 0; i < wa->layers->size(); i++) {
|
||||
layer_to_geojson((*(wa->layers))[i], wa->z, wa->x, wa->y, false, true, false, true, 0, 0, 0, true, state, 0);
|
||||
layer_to_geojson((*(wa->layers))[i], wa->z, wa->x, wa->y, false, true, false, true, 0, 0, 0, true, state, 0, std::set<std::string>());
|
||||
}
|
||||
|
||||
if (fclose(fp) != 0) {
|
||||
@@ -145,7 +145,7 @@ std::vector<mvt_layer> parse_layers(int fd, int z, unsigned x, unsigned y, std::
|
||||
}
|
||||
|
||||
// Reads from the prefilter
|
||||
serial_feature parse_feature(json_pull *jp, int z, unsigned x, unsigned y, std::vector<std::map<std::string, layermap_entry>> *layermaps, size_t tiling_seg, std::vector<std::vector<std::string>> *layer_unmaps, bool postfilter) {
|
||||
serial_feature parse_feature(json_pull *jp, int z, unsigned x, unsigned y, std::vector<std::map<std::string, layermap_entry>> *layermaps, size_t tiling_seg, std::vector<std::vector<std::string>> *layer_unmaps, bool postfilter, key_pool &key_pool) {
|
||||
serial_feature sf;
|
||||
|
||||
while (1) {
|
||||
@@ -223,7 +223,7 @@ serial_feature parse_feature(json_pull *jp, int z, unsigned x, unsigned y, std::
|
||||
}
|
||||
|
||||
drawvec dv;
|
||||
parse_geometry(t, coordinates, dv, VT_MOVETO, "Filter output", jp->line, j);
|
||||
parse_coordinates(t, coordinates, dv, VT_MOVETO, "Filter output", jp->line, j);
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
dv = fix_polygon(dv, false, false);
|
||||
}
|
||||
@@ -354,7 +354,7 @@ serial_feature parse_feature(json_pull *jp, int z, unsigned x, unsigned y, std::
|
||||
// would have already run before prefiltering
|
||||
|
||||
if (v.type != mvt_null) {
|
||||
sf.full_keys.push_back(std::string(properties->value.object.keys[i]->value.string.string));
|
||||
sf.full_keys.push_back(key_pool.pool(std::string(properties->value.object.keys[i]->value.string.string)));
|
||||
sf.full_values.push_back(v);
|
||||
|
||||
if (!postfilter) {
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
struct key_pool;
|
||||
std::vector<mvt_layer> filter_layers(const char *filter, std::vector<mvt_layer> &layer, unsigned z, unsigned x, unsigned y, std::vector<std::map<std::string, layermap_entry>> *layermaps, size_t tiling_seg, std::vector<std::vector<std::string>> *layer_unmaps, int extent);
|
||||
void setup_filter(const char *filter, int *write_to, int *read_from, pid_t *pid, unsigned z, unsigned x, unsigned y);
|
||||
serial_feature parse_feature(json_pull *jp, int z, unsigned x, unsigned y, std::vector<std::map<std::string, layermap_entry>> *layermaps, size_t tiling_seg, std::vector<std::vector<std::string>> *layer_unmaps, bool filters);
|
||||
serial_feature parse_feature(json_pull *jp, int z, unsigned x, unsigned y, std::vector<std::map<std::string, layermap_entry>> *layermaps, size_t tiling_seg, std::vector<std::vector<std::string>> *layer_unmaps, bool filters, key_pool &key_pool);
|
||||
|
||||
+134
-121
@@ -53,7 +53,7 @@ void json_context(json_object *j) {
|
||||
free(s); // stringify
|
||||
}
|
||||
|
||||
void parse_geometry(int t, json_object *j, drawvec &out, int op, const char *fname, int line, json_object *feature) {
|
||||
void parse_coordinates(int t, json_object *j, drawvec &out, int op, const char *fname, int line, json_object *feature) {
|
||||
if (j == NULL || j->type != JSON_ARRAY) {
|
||||
fprintf(stderr, "%s:%d: expected array for geometry type %d: ", fname, line, t);
|
||||
json_context(feature);
|
||||
@@ -72,7 +72,7 @@ void parse_geometry(int t, json_object *j, drawvec &out, int op, const char *fna
|
||||
}
|
||||
}
|
||||
|
||||
parse_geometry(within, j->value.array.array[i], out, op, fname, line, feature);
|
||||
parse_coordinates(within, j->value.array.array[i], out, op, fname, line, feature);
|
||||
}
|
||||
} else {
|
||||
if (j->value.array.length >= 2 && j->value.array.array[0]->type == JSON_NUMBER && j->value.array.array[1]->type == JSON_NUMBER) {
|
||||
@@ -178,6 +178,129 @@ static std::vector<mvt_geometry> to_feature(drawvec &geom) {
|
||||
return out;
|
||||
}
|
||||
|
||||
std::pair<int, drawvec> parse_geometry(json_object *geometry, json_pull *jp, json_object *j,
|
||||
int z, int x, int y, long long extent, bool fix_longitudes, bool mvt_style) {
|
||||
json_object *geometry_type = json_hash_get(geometry, "type");
|
||||
if (geometry_type == NULL) {
|
||||
fprintf(stderr, "Filter output:%d: null geometry (additional not reported): ", jp->line);
|
||||
json_context(j);
|
||||
exit(EXIT_JSON);
|
||||
}
|
||||
|
||||
if (geometry_type->type != JSON_STRING) {
|
||||
fprintf(stderr, "Filter output:%d: geometry type is not a string: ", jp->line);
|
||||
json_context(j);
|
||||
exit(EXIT_JSON);
|
||||
}
|
||||
|
||||
json_object *coordinates = json_hash_get(geometry, "coordinates");
|
||||
if (coordinates == NULL || coordinates->type != JSON_ARRAY) {
|
||||
fprintf(stderr, "Filter output:%d: geometry without coordinates array: ", jp->line);
|
||||
json_context(j);
|
||||
exit(EXIT_JSON);
|
||||
}
|
||||
|
||||
int t;
|
||||
for (t = 0; t < GEOM_TYPES; t++) {
|
||||
if (strcmp(geometry_type->value.string.string, geometry_names[t]) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (t >= GEOM_TYPES) {
|
||||
fprintf(stderr, "Filter output:%d: Can't handle geometry type %s: ", jp->line, geometry_type->value.string.string);
|
||||
json_context(j);
|
||||
exit(EXIT_JSON);
|
||||
}
|
||||
|
||||
drawvec dv;
|
||||
parse_coordinates(t, coordinates, dv, VT_MOVETO, "Filter output", jp->line, j);
|
||||
|
||||
// handle longitude wraparound
|
||||
//
|
||||
// this is supposed to be data for a single tile,
|
||||
// so any jump from the left hand side edge of the world
|
||||
// to the right edge, or vice versa, is unexpected,
|
||||
// so move it to the other side.
|
||||
|
||||
if (fix_longitudes && mb_geometry[t] == VT_POLYGON) {
|
||||
const long long quarter_world = 1LL << 30;
|
||||
const long long world = 1LL << 32;
|
||||
|
||||
bool copy_to_left = false;
|
||||
bool copy_to_right = false;
|
||||
|
||||
for (size_t i = 0; i < dv.size(); i++) {
|
||||
// is this vertex on a different side of the world
|
||||
// than the first vertex? then shift this one to match
|
||||
if (i > 0) {
|
||||
if ((dv[0].x < quarter_world) && (dv[i].x > 3 * quarter_world)) {
|
||||
dv[i].x -= world;
|
||||
}
|
||||
if ((dv[0].x > 3 * quarter_world) && (dv[i].x < quarter_world)) {
|
||||
dv[i].x += world;
|
||||
}
|
||||
}
|
||||
|
||||
// does it stick off the edge of the world?
|
||||
// then we need another copy on the other side of the world
|
||||
if (dv[i].x < 0) {
|
||||
copy_to_right = true;
|
||||
}
|
||||
if (dv[i].x > world) {
|
||||
copy_to_left = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (copy_to_left) {
|
||||
size_t n = dv.size();
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
dv.emplace_back(dv[i].op, dv[i].x - world, (long long) dv[i].y);
|
||||
}
|
||||
}
|
||||
if (copy_to_right) {
|
||||
size_t n = dv.size();
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
dv.emplace_back(dv[i].op, dv[i].x + world, (long long) dv[i].y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
dv = fix_polygon(dv, false, false);
|
||||
}
|
||||
|
||||
// Offset and scale geometry from global to tile
|
||||
for (size_t i = 0; i < dv.size(); i++) {
|
||||
long long scale = 1LL << (32 - z);
|
||||
|
||||
// offset to tile
|
||||
dv[i].x -= scale * x;
|
||||
dv[i].y -= scale * y;
|
||||
|
||||
// scale to tile
|
||||
dv[i].x = std::round(dv[i].x * (extent / (double) scale));
|
||||
dv[i].y = std::round(dv[i].y * (extent / (double) scale));
|
||||
}
|
||||
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
// don't try scaling up because we may have coordinates
|
||||
// on the other side of the world
|
||||
dv = clean_or_clip_poly(dv, z, 256, true, false);
|
||||
if (dv.size() < 3) {
|
||||
dv.clear();
|
||||
}
|
||||
}
|
||||
dv = remove_noop(dv, mb_geometry[t], 0);
|
||||
|
||||
if (mvt_style) {
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
dv = close_poly(dv);
|
||||
}
|
||||
}
|
||||
|
||||
return std::pair<int, drawvec>(t, dv);
|
||||
}
|
||||
|
||||
std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int extent, bool fix_longitudes) {
|
||||
std::map<std::string, mvt_layer> ret;
|
||||
std::shared_ptr<std::string> tile_stringpool = std::make_shared<std::string>();
|
||||
@@ -208,14 +331,6 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
|
||||
continue;
|
||||
}
|
||||
|
||||
json_object *geometry = json_hash_get(j, "geometry");
|
||||
if (geometry == NULL) {
|
||||
fprintf(stderr, "Filter output:%d: filtered feature with no geometry: ", jp->line);
|
||||
json_context(j);
|
||||
json_free(j);
|
||||
exit(EXIT_JSON);
|
||||
}
|
||||
|
||||
json_object *properties = json_hash_get(j, "properties");
|
||||
if (properties == NULL || (properties->type != JSON_HASH && properties->type != JSON_NULL)) {
|
||||
fprintf(stderr, "Filter output:%d: feature without properties hash: ", jp->line);
|
||||
@@ -224,38 +339,6 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
|
||||
exit(EXIT_JSON);
|
||||
}
|
||||
|
||||
json_object *geometry_type = json_hash_get(geometry, "type");
|
||||
if (geometry_type == NULL) {
|
||||
fprintf(stderr, "Filter output:%d: null geometry (additional not reported): ", jp->line);
|
||||
json_context(j);
|
||||
exit(EXIT_JSON);
|
||||
}
|
||||
|
||||
if (geometry_type->type != JSON_STRING) {
|
||||
fprintf(stderr, "Filter output:%d: geometry type is not a string: ", jp->line);
|
||||
json_context(j);
|
||||
exit(EXIT_JSON);
|
||||
}
|
||||
|
||||
json_object *coordinates = json_hash_get(geometry, "coordinates");
|
||||
if (coordinates == NULL || coordinates->type != JSON_ARRAY) {
|
||||
fprintf(stderr, "Filter output:%d: feature without coordinates array: ", jp->line);
|
||||
json_context(j);
|
||||
exit(EXIT_JSON);
|
||||
}
|
||||
|
||||
int t;
|
||||
for (t = 0; t < GEOM_TYPES; t++) {
|
||||
if (strcmp(geometry_type->value.string.string, geometry_names[t]) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (t >= GEOM_TYPES) {
|
||||
fprintf(stderr, "Filter output:%d: Can't handle geometry type %s: ", jp->line, geometry_type->value.string.string);
|
||||
json_context(j);
|
||||
exit(EXIT_JSON);
|
||||
}
|
||||
|
||||
std::string layername = "unknown";
|
||||
json_object *tippecanoe = json_hash_get(j, "tippecanoe");
|
||||
json_object *layer = NULL;
|
||||
@@ -276,88 +359,18 @@ std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int
|
||||
}
|
||||
auto l = ret.find(layername);
|
||||
|
||||
drawvec dv;
|
||||
parse_geometry(t, coordinates, dv, VT_MOVETO, "Filter output", jp->line, j);
|
||||
|
||||
// handle longitude wraparound
|
||||
//
|
||||
// this is supposed to be data for a single tile,
|
||||
// so any jump from the left hand side edge of the world
|
||||
// to the right edge, or vice versa, is unexpected,
|
||||
// so move it to the other side.
|
||||
|
||||
if (fix_longitudes && mb_geometry[t] == VT_POLYGON) {
|
||||
const long long quarter_world = 1LL << 30;
|
||||
const long long world = 1LL << 32;
|
||||
|
||||
bool copy_to_left = false;
|
||||
bool copy_to_right = false;
|
||||
|
||||
for (size_t i = 0; i < dv.size(); i++) {
|
||||
// is this vertex on a different side of the world
|
||||
// than the first vertex? then shift this one to match
|
||||
if (i > 0) {
|
||||
if ((dv[0].x < quarter_world) && (dv[i].x > 3 * quarter_world)) {
|
||||
dv[i].x -= world;
|
||||
}
|
||||
if ((dv[0].x > 3 * quarter_world) && (dv[i].x < quarter_world)) {
|
||||
dv[i].x += world;
|
||||
}
|
||||
}
|
||||
|
||||
// does it stick off the edge of the world?
|
||||
// then we need another copy on the other side of the world
|
||||
if (dv[i].x < 0) {
|
||||
copy_to_right = true;
|
||||
}
|
||||
if (dv[i].x > world) {
|
||||
copy_to_left = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (copy_to_left) {
|
||||
size_t n = dv.size();
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
dv.emplace_back(dv[i].op, dv[i].x - world, (long long) dv[i].y);
|
||||
}
|
||||
}
|
||||
if (copy_to_right) {
|
||||
size_t n = dv.size();
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
dv.emplace_back(dv[i].op, dv[i].x + world, (long long) dv[i].y);
|
||||
}
|
||||
}
|
||||
json_object *geometry = json_hash_get(j, "geometry");
|
||||
if (geometry == NULL) {
|
||||
fprintf(stderr, "Filter output:%d: filtered feature with no geometry: ", jp->line);
|
||||
json_context(j);
|
||||
json_free(j);
|
||||
exit(EXIT_JSON);
|
||||
}
|
||||
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
dv = fix_polygon(dv, false, false);
|
||||
}
|
||||
std::pair<int, drawvec> parsed_geometry = parse_geometry(geometry, jp, j, z, x, y, extent, fix_longitudes, true);
|
||||
|
||||
// Offset and scale geometry from global to tile
|
||||
for (size_t i = 0; i < dv.size(); i++) {
|
||||
long long scale = 1LL << (32 - z);
|
||||
|
||||
// offset to tile
|
||||
dv[i].x -= scale * x;
|
||||
dv[i].y -= scale * y;
|
||||
|
||||
// scale to tile
|
||||
dv[i].x = std::round(dv[i].x * extent / (double) scale);
|
||||
dv[i].y = std::round(dv[i].y * extent / (double) scale);
|
||||
}
|
||||
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
// don't try scaling up because we may have coordinates
|
||||
// on the other side of the world
|
||||
dv = clean_or_clip_poly(dv, z, 256, true, false);
|
||||
if (dv.size() < 3) {
|
||||
dv.clear();
|
||||
}
|
||||
}
|
||||
dv = remove_noop(dv, mb_geometry[t], 0);
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
dv = close_poly(dv);
|
||||
}
|
||||
int t = parsed_geometry.first;
|
||||
drawvec &dv = parsed_geometry.second;
|
||||
|
||||
if (dv.size() > 0) {
|
||||
mvt_feature feature;
|
||||
|
||||
+3
-1
@@ -11,7 +11,9 @@ extern int geometry_within[GEOM_TYPES];
|
||||
extern int mb_geometry[GEOM_TYPES];
|
||||
|
||||
void json_context(json_object *j);
|
||||
void parse_geometry(int t, json_object *j, drawvec &out, int op, const char *fname, int line, json_object *feature);
|
||||
void parse_coordinates(int t, json_object *j, drawvec &out, int op, const char *fname, int line, json_object *feature);
|
||||
std::pair<int, drawvec> parse_geometry(json_object *geometry, json_pull *jp, json_object *j,
|
||||
int z, int x, int y, long long extent, bool fix_longitudes, bool mvt_style);
|
||||
std::vector<mvt_layer> parse_layers(FILE *fp, int z, unsigned x, unsigned y, int extent, bool fix_longitudes);
|
||||
|
||||
serial_val stringify_value(json_object *value, const char *reading, int line, json_object *feature);
|
||||
|
||||
+11
-10
@@ -415,6 +415,7 @@ static void add_scaled_node(struct reader *r, serialization_state *sst, draw g)
|
||||
// called from frontends
|
||||
int serialize_feature(struct serialization_state *sst, serial_feature &sf, std::string const &layername) {
|
||||
struct reader *r = &(*sst->readers)[sst->segment];
|
||||
key_pool key_pool;
|
||||
|
||||
sf.bbox[0] = LLONG_MAX;
|
||||
sf.bbox[1] = LLONG_MAX;
|
||||
@@ -714,7 +715,7 @@ int serialize_feature(struct serialization_state *sst, serial_feature &sf, std::
|
||||
|
||||
bbox_index = encode_index(midx, midy);
|
||||
if (additional[A_CALCULATE_INDEX]) {
|
||||
sf.full_keys.push_back("tippecanoe:index");
|
||||
sf.full_keys.push_back(key_pool.pool("tippecanoe:index"));
|
||||
|
||||
serial_val sv;
|
||||
sv.type = mvt_double;
|
||||
@@ -776,7 +777,7 @@ int serialize_feature(struct serialization_state *sst, serial_feature &sf, std::
|
||||
for (auto &kv : set_attributes) {
|
||||
bool found = false;
|
||||
for (size_t i = 0; i < sf.full_keys.size(); i++) {
|
||||
if (sf.full_keys[i] == kv.first) {
|
||||
if (*sf.full_keys[i] == kv.first) {
|
||||
sf.full_values[i] = kv.second;
|
||||
found = true;
|
||||
break;
|
||||
@@ -784,13 +785,13 @@ int serialize_feature(struct serialization_state *sst, serial_feature &sf, std::
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
sf.full_keys.push_back(kv.first);
|
||||
sf.full_keys.push_back(key_pool.pool(kv.first));
|
||||
sf.full_values.push_back(kv.second);
|
||||
}
|
||||
}
|
||||
|
||||
for (ssize_t i = (ssize_t) sf.full_keys.size() - 1; i >= 0; i--) {
|
||||
coerce_value(sf.full_keys[i], sf.full_values[i].type, sf.full_values[i].s, sst->attribute_types);
|
||||
coerce_value(*sf.full_keys[i], sf.full_values[i].type, sf.full_values[i].s, sst->attribute_types);
|
||||
|
||||
if (prevent[P_SINGLE_PRECISION]) {
|
||||
if (sf.full_values[i].type == mvt_double) {
|
||||
@@ -801,12 +802,12 @@ int serialize_feature(struct serialization_state *sst, serial_feature &sf, std::
|
||||
}
|
||||
}
|
||||
|
||||
if (sf.full_keys[i] == attribute_for_id) {
|
||||
if (*sf.full_keys[i] == attribute_for_id) {
|
||||
if (sf.full_values[i].type != mvt_double && !additional[A_CONVERT_NUMERIC_IDS]) {
|
||||
static bool warned = false;
|
||||
|
||||
if (!warned) {
|
||||
fprintf(stderr, "Warning: Attribute \"%s\"=\"%s\" as feature ID is not a number\n", sf.full_keys[i].c_str(), sf.full_values[i].s.c_str());
|
||||
fprintf(stderr, "Warning: Attribute \"%s\"=\"%s\" as feature ID is not a number\n", sf.full_keys[i]->c_str(), sf.full_values[i].s.c_str());
|
||||
warned = true;
|
||||
}
|
||||
} else {
|
||||
@@ -839,12 +840,12 @@ int serialize_feature(struct serialization_state *sst, serial_feature &sf, std::
|
||||
}
|
||||
|
||||
if (sst->exclude_all) {
|
||||
if (sst->include->count(sf.full_keys[i]) == 0) {
|
||||
if (sst->include->count(*sf.full_keys[i]) == 0) {
|
||||
sf.full_keys.erase(sf.full_keys.begin() + i);
|
||||
sf.full_values.erase(sf.full_values.begin() + i);
|
||||
continue;
|
||||
}
|
||||
} else if (sst->exclude->count(sf.full_keys[i]) != 0) {
|
||||
} else if (sst->exclude->count(*sf.full_keys[i]) != 0) {
|
||||
sf.full_keys.erase(sf.full_keys.begin() + i);
|
||||
sf.full_values.erase(sf.full_values.begin() + i);
|
||||
continue;
|
||||
@@ -854,7 +855,7 @@ int serialize_feature(struct serialization_state *sst, serial_feature &sf, std::
|
||||
if (!sst->filters) {
|
||||
for (size_t i = 0; i < sf.full_keys.size(); i++) {
|
||||
auto ts = sst->layermap->find(layername);
|
||||
add_to_tilestats(ts->second.tilestats, sf.full_keys[i], sf.full_values[i]);
|
||||
add_to_tilestats(ts->second.tilestats, *sf.full_keys[i], sf.full_values[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -867,7 +868,7 @@ int serialize_feature(struct serialization_state *sst, serial_feature &sf, std::
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < sf.full_keys.size(); i++) {
|
||||
sf.keys.push_back(addpool(r->poolfile, r->treefile, sf.full_keys[i].c_str(), mvt_string, r->key_dedup));
|
||||
sf.keys.push_back(addpool(r->poolfile, r->treefile, sf.full_keys[i]->c_str(), mvt_string, r->key_dedup));
|
||||
sf.values.push_back(addpool(r->poolfile, r->treefile, sf.full_values[i].s.c_str(), sf.full_values[i].type, r->value_dedup));
|
||||
}
|
||||
|
||||
|
||||
+38
-1
@@ -6,6 +6,7 @@
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <sys/stat.h>
|
||||
#include "geometry.hpp"
|
||||
#include "mbtiles.hpp"
|
||||
@@ -49,6 +50,42 @@ struct serial_val {
|
||||
serial_val(int t, const std::string &val)
|
||||
: type(t), s(val) {
|
||||
}
|
||||
|
||||
// These functions for interface compatibility with mvt_value:
|
||||
|
||||
serial_val(double val) {
|
||||
type = mvt_double;
|
||||
s = milo::dtoa_milo(val);
|
||||
}
|
||||
|
||||
double to_double() const {
|
||||
return atof(s.c_str());
|
||||
}
|
||||
|
||||
std::string get_string_value() const {
|
||||
return s;
|
||||
}
|
||||
|
||||
void set_string_value(std::string const &val) {
|
||||
type = mvt_string;
|
||||
s = val;
|
||||
}
|
||||
};
|
||||
|
||||
struct key_pool {
|
||||
std::unordered_map<std::string, std::shared_ptr<std::string>> mapping;
|
||||
|
||||
std::shared_ptr<std::string> pool(std::string const &s) {
|
||||
auto f = mapping.find(s);
|
||||
if (f != mapping.end()) {
|
||||
return f->second;
|
||||
}
|
||||
|
||||
std::shared_ptr<std::string> p = std::make_shared<std::string>();
|
||||
*p = s;
|
||||
mapping.emplace(s, p);
|
||||
return p;
|
||||
}
|
||||
};
|
||||
|
||||
struct serial_feature {
|
||||
@@ -75,7 +112,7 @@ struct serial_feature {
|
||||
// to create the keys and values references into the string pool
|
||||
// during initial serialization
|
||||
|
||||
std::vector<std::string> full_keys{};
|
||||
std::vector<std::shared_ptr<std::string>> full_keys{};
|
||||
std::vector<serial_val> full_values{};
|
||||
|
||||
// These fields are generated from full_keys and full_values
|
||||
|
||||
@@ -0,0 +1,565 @@
|
||||
ޢx
|
||||
parsed(�
felt:h3_index SCALERANKNATSCALE LABELRANK
|
||||
FEATURECLANAME NAMEASCIIADM0CAP WORLDCITYMEGACITYSOV0NAMESOV_A3ADM0NAMEADM0_A3ADM1NAMEISO_A2LATITUDE LONGITUDEPOP_MAXPOP_MIN POP_OTHERRANK_MAXRANK_MINLS_NAME MAX_POP10 MAX_POP20 MAX_POP50
|
||||
MAX_POP300
|
||||
MAX_POP310
|
||||
MAX_NATSCA
|
||||
MIN_AREAKM
|
||||
MAX_AREAKM
|
||||
MIN_AREAMI
|
||||
MAX_AREAMI MIN_PERKM MAX_PERKM MIN_PERMI MAX_PERMI
|
||||
MIN_BBXMIN
|
||||
MAX_BBXMIN
|
||||
MIN_BBXMAX
|
||||
MAX_BBXMAX
|
||||
MIN_BBYMIN
|
||||
MAX_BBYMIN
|
||||
MIN_BBYMAX
|
||||
MAX_BBYMAX MEAN_BBXC MEAN_BBYCUN_FIDPOP1950POP1955POP1960POP1965POP1970POP1975POP1980POP1985POP1990POP1995POP2000POP2005POP2010POP2015POP2020POP2025POP2050MIN_ZOOM
|
||||
WIKIDATAIDWOF_IDCAPALTNAME_ENNAME_DENAME_ESNAME_FRNAME_PTNAME_RUNAME_ZHNAME_ARNAME_BNNAME_ELNAME_HINAME_HUNAME_IDNAME_ITNAME_JANAME_KONAME_NLNAME_PLNAME_SVNAME_TRNAME_VINE_IDNAME_FANAME_HENAME_UKNAME_URNAME_ZHT
|
||||
GEONAMESIDfelt:cluster_size,tippecanoe:retain_points_multiplier_sequence)tippecanoe:retain_points_multiplier_firstPOP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990NAMEALTPOP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINTIMEZONEPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990POP_MINPOP1990NAMEPARPOP_MINPOP1990POP_MINPOP1990POP_MINPOP1990"
|
||||
CA"
|
||||
RU"
|
||||
US"
|
||||
CAN"
|
||||
RUS"
|
||||
USA"
|
||||
Kobuk"
|
||||
Alaska"
|
||||
Ambler"
|
||||
Barrow"
|
||||
Canada"
|
||||
Holman"
|
||||
Inuvik"
|
||||
Q79333"
|
||||
Q79350"
|
||||
Q79395"
|
||||
Q79408"
|
||||
Q79714"
|
||||
Q79717"
|
||||
Q79809"
|
||||
Q79810"
|
||||
Q79813"
|
||||
Q80033"
|
||||
Q80133"
|
||||
Russia"
|
||||
巴罗"
|
||||
巴羅"
|
||||
배로"
|
||||
코북"
|
||||
홀만"
|
||||
Atqasuk"
|
||||
Nunavut"
|
||||
Q338686"
|
||||
Q465235"
|
||||
Q602422"
|
||||
Q606622"
|
||||
Q718822"
|
||||
Q719547"
|
||||
Q719585"
|
||||
Q719605"
|
||||
Q719611"
|
||||
Q719707"
|
||||
Q924763"
|
||||
Selawik"
|
||||
Wiseman"
|
||||
İnuvik"
|
||||
|
||||
Enurmino"
|
||||
|
||||
Kaktovik"
|
||||
|
||||
Kivalina"
|
||||
|
||||
Kotzebue"
|
||||
|
||||
Paulatuk"
|
||||
|
||||
Q1013046"
|
||||
|
||||
Q1014088"
|
||||
|
||||
Q1014119"
|
||||
|
||||
Q1020259"
|
||||
|
||||
Q1938137"
|
||||
|
||||
Q2305684"
|
||||
|
||||
Resolute"
|
||||
|
||||
Taloyoak"
|
||||
|
||||
Zvezdnyi"
|
||||
|
||||
Zvezdnyj"
|
||||
|
||||
Zvezdnyy"
|
||||
|
||||
Zvjozdny"
|
||||
|
||||
בארו"
|
||||
|
||||
بارو"
|
||||
|
||||
بیرو"
|
||||
|
||||
کوبک"
|
||||
Allakaket"
|
||||
Enoermino"
|
||||
Enourmino"
|
||||
Kugluktuk"
|
||||
Utqiagvik"
|
||||
Zvëzdnyj"
|
||||
コブク"
|
||||
バロー"
|
||||
剑桥湾"
|
||||
劍橋灣"
|
||||
安布勒"
|
||||
懷斯曼"
|
||||
温赖特"
|
||||
科伯克"
|
||||
科策布"
|
||||
約阿港"
|
||||
育空堡"
|
||||
셀라윅"
|
||||
앰블러"
|
||||
앳쿼석"
|
||||
이누빅"
|
||||
코체부"
|
||||
|
||||
Fort Yukon"
|
||||
|
||||
Gjoa Haven"
|
||||
|
||||
Point Hope"
|
||||
|
||||
Ulukhaktok"
|
||||
|
||||
Utqiaġvik"
|
||||
|
||||
Wainwright"
|
||||
|
||||
Кобук"
|
||||
|
||||
אלקקט"
|
||||
|
||||
אמבלר"
|
||||
|
||||
חולמן"
|
||||
|
||||
קובוק"
|
||||
|
||||
امبلر"
|
||||
|
||||
انوئک"
|
||||
|
||||
رزلوت"
|
||||
|
||||
كوبوك"
|
||||
|
||||
کوبوک"
|
||||
Fort Yukón"
|
||||
Mys Chmidta"
|
||||
Mys Shmidta"
|
||||
Mys Sjmidta"
|
||||
Mys Szmidta"
|
||||
Mys Šmidta"
|
||||
Prudhoe Bay"
|
||||
Tuktoyaktuk"
|
||||
Ikaluktutiak"
|
||||
Mys Schmidta"
|
||||
Resolute Bay"
|
||||
Tsiigehtchic"
|
||||
Άμπλερ"
|
||||
Χόλμαν"
|
||||
Інувік"
|
||||
Амблер"
|
||||
Барроу"
|
||||
Инувик"
|
||||
Коцебу"
|
||||
וויזמן"
|
||||
טלויאק"
|
||||
קוצביו"
|
||||
רזולוט"
|
||||
أمبلير"
|
||||
إنوفيك"
|
||||
ایمبلر"
|
||||
سلاویک"
|
||||
سیلاوک"
|
||||
سیگیچک"
|
||||
هولمان"
|
||||
وایزمن"
|
||||
پالتاک"
|
||||
پولاٹک"
|
||||
ہولمان"
|
||||
努勒維特"
|
||||
因纽维克"
|
||||
基瓦利纳"
|
||||
塔洛約克"
|
||||
塞拉威克"
|
||||
施密特岬"
|
||||
波拉圖克"
|
||||
雷索卢特"
|
||||
齊格奇克"
|
||||
뚝토약툭"
|
||||
리솔루트"
|
||||
알라카켓"
|
||||
와이즈맨"
|
||||
치이게치"
|
||||
키발리나"
|
||||
탈로요크"
|
||||
파울라툭"
|
||||
Cambridge Bay"
|
||||
United States"
|
||||
포트 유콘"
|
||||
America/Inuvik"
|
||||
America/Juneau"
|
||||
Campbridge Bay"
|
||||
Fort McPherson"
|
||||
Prudhoe-öböl"
|
||||
Ινούβικ"
|
||||
Κομπούκ"
|
||||
Μπάροου"
|
||||
Σελαβίκ"
|
||||
Аткасук"
|
||||
Вайзмен"
|
||||
Полатук"
|
||||
Селавик"
|
||||
Селавік"
|
||||
Уайзмен"
|
||||
Цігечик"
|
||||
אטקאסוק"
|
||||
אינוויק"
|
||||
סלאוויק"
|
||||
קקטוביק"
|
||||
آتکاسوک"
|
||||
آلاکاکت"
|
||||
اینوویک"
|
||||
ایٹقاسک"
|
||||
بولاتوك"
|
||||
تالویوک"
|
||||
تسیگچیک"
|
||||
زویزڈنج"
|
||||
سيلاويك"
|
||||
كوتزبيو"
|
||||
نورمینو"
|
||||
وائزمین"
|
||||
وينرايت"
|
||||
وینرائٹ"
|
||||
وینرایت"
|
||||
ٹالویوک"
|
||||
کگ لک تک"
|
||||
کیکٹووک"
|
||||
Populated place"
|
||||
جوا ہیون"
|
||||
وايز مان"
|
||||
پرودو بی"
|
||||
कोबुक"
|
||||
কসেভো"
|
||||
কোবুক"
|
||||
アンブラー"
|
||||
エヌルミノ"
|
||||
カクトビク"
|
||||
キバリーナ"
|
||||
セラウィク"
|
||||
タロヨーク"
|
||||
ワイズマン"
|
||||
卡克托维克"
|
||||
普拉德霍湾"
|
||||
普拉德霍灣"
|
||||
波因特霍普"
|
||||
烏路卡托克"
|
||||
艾奴爾米諾"
|
||||
艾特跨萨克"
|
||||
茲韋茲德尼"
|
||||
迈斯施密达"
|
||||
阿拉卡基特"
|
||||
麥克弗森堡"
|
||||
麦克弗森堡"
|
||||
에누르미노"
|
||||
웨인라이트"
|
||||
카크토비크"
|
||||
쿠글룩투크"
|
||||
America/Resolute"
|
||||
Vịnh Cambridge"
|
||||
Αλακάκετ"
|
||||
Ατκασούκ"
|
||||
Ζβέζντιγ"
|
||||
Κάκτοβικ"
|
||||
Κιβαλίνα"
|
||||
Ρεσολούτ"
|
||||
Вейнрайт"
|
||||
Енурміно"
|
||||
Звёздный"
|
||||
Кактовик"
|
||||
Кактовік"
|
||||
Кивалина"
|
||||
Паулатук"
|
||||
Резольют"
|
||||
Талойоак"
|
||||
Уэйнрайт"
|
||||
Цигетчик"
|
||||
Энурмино"
|
||||
אנורמינו"
|
||||
ויינרייט"
|
||||
פאולאטוק"
|
||||
ציג'טצ'יק"
|
||||
קיוולינה"
|
||||
أتكيوسوك"
|
||||
ألاكاكيت"
|
||||
إنورمينو"
|
||||
انورمینو"
|
||||
تالويواك"
|
||||
ریسولوتی"
|
||||
زووزدنیج"
|
||||
زيفازدني"
|
||||
كاكتوفيك"
|
||||
كيفالينا"
|
||||
کاکتوویک"
|
||||
کوتزابیو"
|
||||
کوتزهبیو"
|
||||
کیوالینا"
|
||||
미스 슈미타"
|
||||
조아 헤이븐"
|
||||
포인트 호프"
|
||||
포트 맥퍼슨"
|
||||
프루도 베이"
|
||||
Форт-Юкон"
|
||||
פוינט הופ"
|
||||
פרודו ביי"
|
||||
بوينت هوب"
|
||||
پروڈہو بے"
|
||||
پوینت هوپ"
|
||||
کمبریج بی"
|
||||
کیمبرج بے"
|
||||
Γουάιζμαν"
|
||||
Ενουρμίνο"
|
||||
Ταλογιοάκ"
|
||||
Аллакакет"
|
||||
Звьоздний"
|
||||
Куглуктук"
|
||||
זביוזדניי"
|
||||
ایلاکاکیٹ"
|
||||
كوغلوكتوك"
|
||||
مایسشمیتا"
|
||||
ٹکٹویاکٹک"
|
||||
کوگلاکتوک"
|
||||
इनुविक"
|
||||
होलमैन"
|
||||
ইনুভিক"
|
||||
アラカキット"
|
||||
イヌヴィック"
|
||||
コッツビュー"
|
||||
シュミット岬"
|
||||
ズヴョズニィ"
|
||||
パウラトゥク"
|
||||
プルードー湾"
|
||||
レゾリュート"
|
||||
伊尔库茨克州"
|
||||
图克托亚图克"
|
||||
圖克托亞圖克"
|
||||
库格鲁克图克"
|
||||
庫格魯克圖克"
|
||||
즈베즈드니이"
|
||||
Μις Σμίντα"
|
||||
Йоа-Гейвен"
|
||||
Йоа-Хейвен"
|
||||
Мис Барроу"
|
||||
Мис Шмідта"
|
||||
Мыс Шмидта"
|
||||
Пойнт-Гоуп"
|
||||
Пойнт-Хоуп"
|
||||
Прадхо-Бей"
|
||||
מיס שמידטה"
|
||||
פורט יוקון"
|
||||
جیجوی هیون"
|
||||
فورت يوكون"
|
||||
فورت یوکان"
|
||||
فورٹ یوکون"
|
||||
ميس شميدتا"
|
||||
پوائنٹ ہوپ"
|
||||
캠브리지 베이"
|
||||
Γουέινραϊτ"
|
||||
Κοτζέμπουε"
|
||||
Παουλατούκ"
|
||||
Тактояктук"
|
||||
Туктояктук"
|
||||
Улукхакток"
|
||||
גג'ואה האבן"
|
||||
טוקטויאטוק"
|
||||
קוגלולקטוק"
|
||||
اولاکاکچوک"
|
||||
تسيجيهتشيك"
|
||||
Northwest Territories"
|
||||
Γκόα Χέιβεν"
|
||||
Πόιντ Χόουπ"
|
||||
خليج برودهو"
|
||||
فورٹ مکفرسن"
|
||||
مئیس شمیدتا"
|
||||
ऐम्ब्लर"
|
||||
पॉलातुक"
|
||||
रेसोलूट"
|
||||
वेनराइट"
|
||||
सेलाविक"
|
||||
ওয়েনরিট"
|
||||
তালোয়াক"
|
||||
সেলাউইক"
|
||||
アットカサック"
|
||||
ウェインライト"
|
||||
クグルクトゥク"
|
||||
ポイントホープ"
|
||||
توكتوياكتوك"
|
||||
توکتویاکتوک"
|
||||
Προύντο Μπέι"
|
||||
Φορτ Γιούκον"
|
||||
פורט מקפרסון"
|
||||
خليج كامبردج"
|
||||
قرية ريزولوت"
|
||||
Chukchi Autonomous Okrug"
|
||||
United States of America"
|
||||
Τσιγκέχτσιτς"
|
||||
فورت مک فرسون"
|
||||
قرية غوا هافن"
|
||||
अत्कासुक"
|
||||
अलाकाकेट"
|
||||
इनर्मिनो"
|
||||
किवालिना"
|
||||
टालोयोआक"
|
||||
वाइज़मैन"
|
||||
কাকতোভিক"
|
||||
কিভালিনা"
|
||||
পাউলাটুক"
|
||||
রেজোলিউট"
|
||||
সিগেটছিক"
|
||||
হোলম্যান"
|
||||
ウルクハクトーク"
|
||||
ジョア・ヘイヴン"
|
||||
トサイイジチック"
|
||||
フォートユーコン"
|
||||
Кеймбридж-Бей"
|
||||
Кеймбрідж-Бей"
|
||||
فورت ماكفرسون"
|
||||
पॉइंट होप"
|
||||
Κουγκλουκτούκ"
|
||||
קיימברדיג' ביי"
|
||||
Κέιμπριτζ Μπέι"
|
||||
Φορτ ΜακΦέρσον"
|
||||
Форт-Макферсон"
|
||||
काक्तोविक"
|
||||
এনিউরমিনো"
|
||||
ケンブリッジ・ベイ"
|
||||
トゥクトヤクトゥク"
|
||||
Τουκτογιάκτουκ"
|
||||
ज़ोआ हेवेन"
|
||||
प्रुदहो बे"
|
||||
मिस श्मिता"
|
||||
পয়েন্ট হোপ"
|
||||
ফোর্ট ইউকন"
|
||||
उत्कीग्विक"
|
||||
कुग्लुकटूक"
|
||||
कोटज़ेबूये"
|
||||
অ্যাম্বলার"
|
||||
আল্লাকাকেত"
|
||||
উৎকিয়াভিক"
|
||||
ওয়াইজম্যান"
|
||||
কুগ্লুকটুক"!
|
||||
গজোয়া হাভেন"!
|
||||
প্রুদোহে বে"#
|
||||
!त्सीगेत्चिक"#
|
||||
!フォートマクファーソン"$
|
||||
"कैम्ब्रिज बे"$
|
||||
"फ़ोर्ट यूकॉन"$
|
||||
"কেম্ব্রিজ বে"$
|
||||
"মাইস শ্মিদতা"&
|
||||
$অ্যাটকোয়াসুক"&
|
||||
$জেভেজদিনিয়াজ"&
|
||||
$তুকতোইয়াকতুক")
|
||||
'ज़्वेज़्दनिज़")
|
||||
'तुक्तोयाक्तुक"0
|
||||
.फ़ोर्ट मैकफ़र्सन"0
|
||||
.ফোর্ট ম্যাকফারসন" | ||||