Compare commits

..

230 Commits

Author SHA1 Message Date
02b020a2c7 Run the JSON parsers in separate threads 2015-10-27 16:48:02 -07:00
4289f9ba98 Segment the file into input chunks. Allow commas at the top level. 2015-10-26 18:02:03 -07:00
f53b808dac Move setup for geometry serialization thread out by a level 2015-10-26 17:00:55 -07:00
10d85fe507 Use memory-mapped I/O for GeoJSON reading if possible 2015-10-26 16:05:01 -07:00
ff847f6df3 Fix memory leak 2015-10-23 16:39:53 -07:00
e735678e4b Pass the input line number around instead of the JSON parser itself 2015-10-23 16:36:03 -07:00
8214218bd7 Run serialization on a separate thread from JSON parsing 2015-10-23 16:31:59 -07:00
b0fb0b5c75 Locking around things that can touch the global parse tree 2015-10-23 16:00:07 -07:00
a3263d521f Make an event queue of geometries 2015-10-23 15:41:24 -07:00
73e77b5655 Add a function to disconnect a JSON object from the parse tree 2015-10-23 14:58:50 -07:00
6d438b9c7b Factor out the JSON token reading loop 2015-10-23 11:59:57 -07:00
3fbd0d6546 Merge pull request #100 from mapbox/multithread
Multithreaded tile generation
2015-10-20 15:21:46 -07:00
e554a121fb Fix formatting 2015-10-20 12:03:49 -07:00
eb5c78482a Bump version number and add changelog details 2015-10-20 12:01:10 -07:00
a38668a6da Add volatile declaration to shared global variables 2015-10-20 10:15:02 -07:00
e4e14b2078 Link with -lpthread 2015-10-19 15:43:06 -07:00
965176f254 Make the progress indicator more aware of progress in other threads 2015-10-19 14:19:32 -07:00
56910fd016 Restore the error message when a tile can't be made small enough 2015-10-19 13:39:44 -07:00
3bb87227ec Locking for map center 2015-10-19 13:32:02 -07:00
ef5fdf4036 Locking around the file keys 2015-10-19 13:26:47 -07:00
3da692250d Remove unused argument 2015-10-19 13:11:00 -07:00
5d014e040f Actually create threads and hand the tiling tasks off to them 2015-10-19 12:32:40 -07:00
10bd608b9e Merge branch 'master' into multithread 2015-10-19 11:58:01 -07:00
89e1640496 Add a decode option to translate the entire tileset into GeoJSON 2015-10-19 11:12:11 -07:00
3ad499fa44 Merge branch 'master' into multithread
Conflicts:
	tile.cc
2015-10-19 10:31:13 -07:00
f64e2c94e2 Let Clipper clean up polygons again after scaling and simplification 2015-10-15 17:11:29 -07:00
6a1895547d Reduce minimum polygon size for less blocky appearance 2015-10-14 15:51:11 -07:00
37ffacdeb9 Merge pull request #98 from mapbox/polygon-ring2
Clean up polygon encoding, and use Clipper for polygon clipping
2015-10-14 12:49:21 -07:00
592c47c549 Fix signedness warnings 2015-10-14 12:24:18 -07:00
2b25c2fe3e Use Clipper for polygon clipping instead of my own implementation 2015-10-13 17:25:01 -07:00
77b451f2c8 Fix some rounding error just by staying in double precision 2015-10-12 12:51:55 -07:00
8c5681a582 Still not quite a round trip from GeoJSON to GeoJSON, but getting closer 2015-10-09 16:45:34 -07:00
0b47471777 Most of the way to making decode output GeoJSON 2015-10-09 12:41:28 -07:00
329f041bf2 Remove closepath expectation in polygon thinning 2015-10-07 17:11:58 -07:00
5dc9f50345 Clean up polygon generation and clipping. Make sure winding order is correct. 2015-10-07 16:52:52 -07:00
7154c29459 Merge pull request #96 from mapbox/featurezooms
Support per-feature zoom level specification
2015-10-07 14:00:53 -07:00
a8e2b2d55a More formatting correction 2015-10-07 13:59:06 -07:00
9bd2f70516 Fix formatting 2015-10-07 13:57:41 -07:00
6767aa7a5c Add documentation. Be lenient about zooms as numbers vs strings. 2015-10-07 13:54:31 -07:00
c0480673c5 Add a tippecanoe-specific GeoJSON extension for feature minzoom and maxzoom. 2015-10-06 16:51:23 -07:00
c4f517fbdc Merge pull request #94 from mapbox/bare-geometry
Detect and parse bare GeoJSON geometries that aren't part of a Feature
2015-10-06 10:59:55 -07:00
3a94106283 Don't write out tiles that have no features. 2015-09-30 11:56:22 -07:00
5ef4ea4b27 Detect and parse bare GeoJSON geometries that aren't part of a Feature 2015-09-25 17:28:15 -07:00
18647d18dd Merge pull request #84 from mapbox/tile-join-docs
Forgot to update manpage
2015-09-17 12:55:27 -07:00
1a874740cf Forgot to update manpage 2015-09-17 12:54:38 -07:00
a70a8734e3 Merge pull request #83 from mapbox/tile-join-docs
Simplify tile-join example
2015-09-17 12:52:37 -07:00
68c81162b5 Simplify tile-join example 2015-09-17 12:51:26 -07:00
efe378a075 Merge pull request #82 from mapbox/tile-join-docs
Document tile-join
2015-09-17 12:18:01 -07:00
684e995cda Document tile-join 2015-09-17 12:10:17 -07:00
763bc36563 Add const to fix the build 2015-09-15 13:23:34 -07:00
4dd28f7aae Merge pull request #80 from mapbox/preserve-sequence
Add a flag to preserve the original order of the features from the input
2015-09-14 15:45:51 -07:00
b5f374cdac Fix typo 2015-09-14 15:43:16 -07:00
5ab2673b8c Add a flag to preserve the original order of the features from the input 2015-09-14 15:42:06 -07:00
601c092883 Merge branch 'master' into multithread
Conflicts:
	tile.cc
2015-09-14 10:52:58 -07:00
93b2687548 Merge pull request #79 from mapbox/rework-options
Change some defaults that keep tripping people up:
2015-08-27 16:10:20 -07:00
541d3518c2 Change some defaults that keep tripping people up:
* Default detail with low -z is only 13, so it works with GL
* No coalescing, line-reversing, or reordering by attributes except by request
* Dropping lines like points moves from -d to -a
2015-08-27 16:04:37 -07:00
d9974d3cc7 Merge pull request #78 from mapbox/geometrycollection
Support GeometryCollection input
2015-08-25 13:17:57 -07:00
b3302c30e5 Add a flag to retain only the features that match the join CSV 2015-08-24 14:12:46 -07:00
a57b010245 Add a feature to exclude a key from the joined tileset 2015-08-21 17:32:44 -07:00
2814f1ccf2 Check for tiles that are too large to upload. 2015-08-21 14:37:55 -07:00
f99d320253 Fix indent style 2015-08-21 14:27:41 -07:00
ea6775f124 Merge pull request #77 from mapbox/tile-join
Tool to join new CSV data to an existing tileset
2015-08-21 14:12:34 -07:00
88b9750959 Fix a bunch of compiler warnings 2015-08-21 10:33:57 -07:00
529c9aedc9 Join attributes from CSV into tiles 2015-08-20 17:32:58 -07:00
9a49534098 Parse the CSV 2015-08-20 16:45:45 -07:00
514da467e1 Only allow one input .mbtiles because multiple really won't work 2015-08-20 16:15:09 -07:00
06ca52526d Copy the bounding box, center, zoom range metadata over 2015-08-20 16:12:34 -07:00
f131987f9e Fix my pointer confusion around the global keys per layer 2015-08-20 15:48:11 -07:00
29fcbf244d Write each tile to the destination tileset 2015-08-20 15:15:47 -07:00
2e3f03172e Write out the JSON part of the metadata 2015-08-20 15:01:34 -07:00
ed378681e4 Making the string pool in the destination tile 2015-08-20 14:50:26 -07:00
863c9a5929 Retrieving keys and values 2015-08-20 14:27:39 -07:00
224321f6c8 Look up layers by name because the source mbtiles might be inconsistent 2015-08-20 13:42:24 -07:00
caafe2fe5c Checkpoint in copying data from one tileset to another 2015-08-20 13:02:34 -07:00
86d5a542de Fix typo 2015-08-15 09:50:04 -04:00
9761010550 Downgrade no-features error to a warning 2015-08-11 16:55:54 -07:00
bc661ef3d5 Most of the way to handling GeometryCollections 2015-08-11 16:51:44 -07:00
b4339b2f75 Merge pull request #75 from mapbox/memleak
Fix memory leak when closing out tiles that had all features clipped away
2015-07-29 16:49:37 -07:00
a51ddbe180 Fix misspelling in ifdef 2015-07-29 16:46:56 -07:00
10e35c4300 Fix memory leak when closing out tiles that had all features clipped away 2015-07-29 16:45:41 -07:00
39cd5e210e Merge pull request #74 from mapbox/doc
Actually using the stock sqlite on MacOS
2015-07-28 12:49:54 -07:00
70d11cc335 Actually using the stock sqlite on MacOS 2015-07-28 12:48:49 -07:00
b115a07005 Merge pull request #73 from mapbox/doc
Clarify documentation for dependencies
2015-07-28 12:39:40 -07:00
26beada6bd Clarify documentation for dependencies 2015-07-28 12:38:35 -07:00
32179b7ad6 Merge pull request #72 from mapbox/manylayers
Don't crash if there are more than 128 layers
2015-07-27 16:21:24 -07:00
cc05f46fb7 Don't crash if there are more than 128 layers 2015-07-27 16:20:20 -07:00
b897712a10 Remove double-setting of thread task list 2015-07-23 16:24:50 -07:00
a64913c989 Move the data that threads will need into a parameter block 2015-07-23 16:17:23 -07:00
41faf3a5c2 Merge branch 'master' of https://github.com/mapbox/tippecanoe 2015-07-14 17:12:02 -07:00
e01ea076ed Fix double-free crash with unsupported property type 2015-07-14 17:11:34 -07:00
506c801c65 Merge pull request #67 from mapbox/overflow
Fix buffer overflow in length of temporary file names
2015-07-10 10:27:39 -07:00
9d25afa41f Fix buffer overflow in length of temporary file names 2015-07-10 10:26:23 -07:00
d55af3b3a6 Run through the thread task queues, although still sequentially 2015-07-09 16:14:24 -07:00
2957f16b4b Assign tasks (reading temporary files) to threads 2015-07-09 16:09:40 -07:00
2bdb51e995 Calculate how many threads should be run 2015-07-09 15:24:47 -07:00
5d4ab6df1b Merge branch 'master' into multithread 2015-07-08 16:36:58 -07:00
3452ee92ab Fix formatting 2015-07-08 16:35:02 -07:00
e5a6f981b7 Work out the sharding math for multhreading 2015-07-08 16:33:22 -07:00
9abf09eb7e Merge pull request #66 from mapbox/quiet
Add option to quiet the chatty progress indicators
2015-07-08 15:07:15 -07:00
138699d243 Add option to quiet the chatty progress indicators 2015-07-08 15:06:21 -07:00
3bc5a07e5b Merge pull request #65 from mapbox/skip
Skipping over zoom levels when there is a minzoom, and other performance improvements
2015-07-08 14:44:55 -07:00
e7b4443838 Enforce code style 2015-07-08 14:42:12 -07:00
0ff6819efb Merge branch 'skip' of https://github.com/mapbox/tippecanoe into skip 2015-07-08 12:26:01 -07:00
6fd72d4518 Remove vacuum step, which doesn't seem to make .mbtiles smaller in practice
yet takes time and temporary disk space to perform
2015-07-08 12:25:09 -07:00
c19c913bf9 Fix confusing error messages from sloppy copying and pasting of code 2015-07-08 11:52:22 -07:00
65f3737325 Use a hash table of binary trees for string pooling, not just a binary tree 2015-07-02 17:07:11 -07:00
1b72804358 Precalculate which child tiles a feature can overlap
instead of looping over them all
2015-07-01 15:17:35 -07:00
18cdcb0732 Drop linetos smaller than tile resolution before doing normal simplification
Since simplification is rather expensive
2015-07-01 12:16:50 -07:00
af13a95dc1 Shard the child tiles as widely as possible even if not skipping levels
to help future parallelization
2015-07-01 12:04:45 -07:00
b2fdcba6b0 Don't shrink string buffers before returning them as JSON objects.
It takes a little bit of time, and it's already not shrinking them
for number objects.
2015-06-30 17:21:48 -07:00
767a581874 Inline the I/O in the long long serialization loop 2015-06-30 16:47:25 -07:00
0d3192b863 Break rewriting the geometry for the next zoom out into its own function 2015-06-30 16:36:26 -07:00
530852ae00 Maximum zoom increment of 3 seems to give the best performance 2015-06-29 17:18:48 -07:00
4bb88e228a Fix buffer overflow. This is a bounding box, not sub-tiles. 2015-06-29 17:05:33 -07:00
7724e2c329 Skip over some intermediate zooms below minzoom 2015-06-29 16:42:26 -07:00
efe3c62bb1 Start breaking the assumption that the next zoom is exactly 1 deeper 2015-06-29 16:16:38 -07:00
6951c1b72d Merge pull request #62 from mapbox/signedchars
Fix sign-extension bug with signed chars in attribute names
2015-06-21 11:20:23 -07:00
59faead7fa Fix sign-extension bug with signed chars in attribute names 2015-06-21 11:18:14 -07:00
a42dbd7968 Merge pull request #60 from mapbox/cpp
Move zoom traversal into C++
2015-06-19 17:46:31 -07:00
62052cafab Move zoom traversal into C++ 2015-06-19 17:29:56 -07:00
265b6866db Merge pull request #59 from mapbox/smaller
Optimizations to reduce memory footprint
2015-06-19 17:08:00 -07:00
e6c5aa9bfe Oops. Make sure that 0 stays 0 even when swizzling 2015-06-19 15:53:09 -07:00
e6997b00ff Swizzle the string comparison so it's not pathological if input is presorted 2015-06-19 15:49:51 -07:00
498e723563 Yet another temp file, for the tree of pointers into the string pool 2015-06-18 16:30:51 -07:00
dc3021656e Build the string pool in an appendable memory map, not the normal heap 2015-06-18 16:13:37 -07:00
d96dee8dad Fix formatting again 2015-06-18 14:47:29 -07:00
a185073f0a Shrink the geometry by shaving off bits below the maxzoom tile resolution 2015-06-18 14:16:16 -07:00
86a341c344 Merge pull request #57 from anandthakker/master
Update README to include buffer size units.
2015-06-18 12:39:00 -07:00
a1d3ecf9bb Save another byte per attribute by moving the type to the string pool 2015-06-18 12:12:20 -07:00
1a44538bdf Use the first coordinates of the first feature as the origin for deltas 2015-06-18 10:52:01 -07:00
46626e4f08 Delta encoding for motion within features 2015-06-17 17:48:29 -07:00
725ea71e57 Fix formatting 2015-06-17 17:30:17 -07:00
cde1e60603 Use a string pool to avoid duplicating keys and values 2015-06-17 17:18:08 -07:00
55e93a5d37 Use variable-length zigzag for ints and long longs 2015-06-17 16:46:36 -07:00
fbe4416fba Update README to include buffer size units. 2015-06-17 15:18:46 -04:00
9b34f7e6e3 Merge pull request #54 from mapbox/badproj
Clearer message about projections
2015-06-05 10:35:02 -07:00
d5d322f36a Clearer message about projections 2015-06-05 10:34:19 -07:00
448617e0a7 Merge pull request #53 from mapbox/badproj
Add a warning when data appears to be in the wrong projection
2015-06-05 10:24:13 -07:00
263ae94e75 Add a warning when data appears to be in the wrong projection 2015-06-05 10:23:25 -07:00
1a95504390 No need to write a loop to reindent the source files 2015-06-03 12:47:56 -07:00
b70d19288e Get back in sync with json-pull 2015-06-03 11:31:41 -07:00
fd60cc6600 Merge pull request #52 from mapbox/indentstyle
Consistent indent style with clang-format
2015-06-03 11:26:25 -07:00
8a1f0d83e1 Consistent indent style with clang-format 2015-06-03 11:22:13 -07:00
d9ff3f78fc Update manpage 2015-06-03 11:12:01 -07:00
34b00eca73 Merge pull request #50 from mapbox/gl4096
Add a command line option to specify the minimum allowed tile extent
2015-06-03 11:11:16 -07:00
70291f0415 Merge pull request #51 from mapbox/fitsample
Flag to drop as many features as necessary to keep tiles under the size limit
2015-06-03 11:10:54 -07:00
bc2f243f0b Add a command line option to specify the minimum allowed tile extent 2015-06-01 15:01:46 -07:00
6341419229 Punctuation and capitalization 2015-06-01 10:18:03 -07:00
c048311124 Merge pull request #48 from brunosan/patch-1
Fix RunKeeper link
2015-05-29 12:56:38 -04:00
25072133fb Install manpage 2015-05-29 12:15:48 -04:00
142ea37e17 use md2man to generate man page 2015-05-29 12:06:41 -04:00
4001df81cc Fix RunKeeper link 2015-05-29 12:05:12 -04:00
22471ab5be Use markdown syntax instead of html syntax where appropriate 2015-05-29 12:04:15 -04:00
79a08edcf6 Add atmospheric river 2015-05-29 11:55:17 -04:00
4eaa740f55 Add made with. Fixes #47 2015-05-29 11:50:04 -04:00
2a6af266b7 Add -v to changelog 2015-05-29 10:52:20 -04:00
5ba8f2f866 Merge pull request #45 from mapbox/version-flag
Add tippecanoe -v
2015-05-29 10:51:30 -04:00
95997b50c4 Add tippecanoe -v 2015-05-29 10:50:11 -04:00
67fe27f70a Add changelog with entry for 1.2.0 2015-05-29 10:44:39 -04:00
a4c79e1ec2 Merge pull request #41 from mapbox/minzoom-maxzoom
Add check that min is less than max. Fixes #40
2015-05-29 10:33:59 -04:00
ce6a1aac88 Only error if minzoom is truly greater, not just equal 2015-05-29 10:33:44 -04:00
a0693446d5 Add check that min is less than max. Fixes #40 2015-05-28 13:21:38 -04:00
38dc80ec68 Fix a place that wasn't checking for disk write errors 2015-05-20 18:04:34 -07:00
901f6a76b6 Keep the resolution constant as we drop features to make the tile fit 2015-05-20 15:15:45 -07:00
555ababd2e Add a flag to dynamically drop a fraction of features from large tiles 2015-05-20 14:57:00 -07:00
9e162e6f8f Merge pull request #35 from mapbox/dropline
Add a flag to let you drop lines at low zooms just like points
2015-04-28 16:50:46 -07:00
1381f0f276 Add a flag to let you drop lines at low zooms just like points 2015-04-21 08:19:51 -07:00
12fb2c969c Merge pull request #34 from mapbox/warn-no-features
Warn if no Features have been seen after 50 JSON hashes are closed.
2015-04-17 10:49:36 -07:00
167ec690a0 Warn if no Features have been seen after 50 JSON hashes are closed. 2015-04-17 10:48:03 -07:00
4f9edf7f29 Merge pull request #32 from mapbox/proto-doc
Add Linux package requirement to readme
2015-04-17 10:13:06 -07:00
fa9474ff74 Add Linux package requirement to readme 2015-04-17 10:12:11 -07:00
d64328ac35 Fix crash when encoding boolean properties. 2015-04-10 13:03:11 -07:00
ed2f968b4e Merge pull request #30 from mapbox/prevent
Add flags to disable simplification/coalescing/limits when unwanted
2015-04-10 12:00:26 -07:00
4041811372 Add flags to disable simplification/coalescing/limits when unwanted 2015-04-10 11:36:30 -07:00
85919de490 Merge pull request #29 from mapbox/multilayer
Add multilayer support
2015-04-10 11:03:36 -07:00
e66d976d55 Merge pull request #28 from mapbox/dot-size-doc
Dot size doc
2015-03-26 11:28:43 -07:00
1f8581c76c More doc text 2015-03-26 11:28:06 -07:00
0cd733eb77 Document how to choose high-zoom dot sizes 2015-03-26 11:25:56 -07:00
fd8de691eb Fix layer name crash when reading from the standard input 2015-03-25 14:07:34 -07:00
e95cc82678 Revise documentation for multiple layers 2015-03-25 14:00:27 -07:00
a076c5619d Merge remote-tracking branch 'origin/master' into multilayer 2015-03-25 13:54:29 -07:00
86925eea4c Write out geometries for all layers 2015-03-24 17:07:51 -07:00
73b63133e2 Multiple layers make it into JSON metadata now 2015-03-24 16:28:31 -07:00
2198bcc2a6 Handle multiple reading. Multiple writing still crashes. 2015-03-23 17:44:23 -07:00
f5135ebc63 Writing out the JSON metadata for multiple layers 2015-03-23 16:12:12 -07:00
eb24c6e21e Include the layer number in the serialized geometry 2015-03-23 15:37:49 -07:00
3d074653b5 Merge pull request #26 from mapbox/gammafix
Clean up the gamma semantics a little.
2015-03-23 14:47:17 -07:00
a880f44a91 Clean up the gamma semantics a little.
0 (default) means unchanged: maxzoom contains all the dots.

Positive number, even very small, means that it still checks
the gap size and thins out dots if they are close together.

Negative number means no gamma, and the old random instead of
uniform dropping at low zooms.
2015-03-23 14:44:21 -07:00
8002609f0c More scaffolding for multiple layers 2015-03-23 13:44:35 -07:00
d370b07231 Scaffolding for layers within per-tile processing 2015-03-23 11:36:35 -07:00
26bcdef06b Clarify that base and lowest zoom are the same as maxzoom and minzoom 2015-03-18 17:23:08 -07:00
4549d1e4f4 Merge pull request #22 from mapbox/uniform
Add a dot-density gamma feature to thin out especially dense clusters
2015-03-09 13:54:33 -07:00
f3e051a610 Clarify and correct gamma in README 2015-03-09 12:36:15 -07:00
b59a251924 Write metadata even when tiling fails so you can look at the partial map 2015-03-06 16:33:32 -08:00
f0a8e5b192 Progress indicator for reordering geometry 2015-03-06 15:32:52 -08:00
9343c5fcc1 Less wordy message about sorting 2015-03-06 14:35:39 -08:00
816ef2eca8 Flesh out README a little 2015-03-06 14:32:53 -08:00
41b28b2a1b Add dot gamma processing 2015-03-06 13:12:32 -08:00
558a7a412c Restore old code for uniform instead of random dot-dropping
From ffe4c95376
2015-03-06 10:56:02 -08:00
572df8ad39 Reorder the geometries by index 2015-03-05 16:18:01 -08:00
a8b2db8d5a Fix double-close of top level geometry file 2015-03-05 15:22:58 -08:00
97d65e6b7d Bring back the index: now just a file position and a point 2015-03-05 15:15:56 -08:00
b2eff13667 Impose a limit on the maximum number of features in a tile. 2014-12-19 14:33:39 -08:00
290e39f80c Improve function name 2014-12-19 11:56:40 -08:00
0b84f13159 Merge pull request #18 from mapbox/topdown
Work from the top down instead of from feature indices
2014-12-18 11:28:40 -08:00
5a2003cb2c Fix failure to release temporary memory 2014-12-17 17:41:57 -08:00
32010fc893 Oops. Do a lot less progress indicator I/O. 2014-12-17 17:08:04 -08:00
48b5db6ae5 Keep the progress indicator progressing while working through big tiles 2014-12-17 16:01:33 -08:00
7f3551070e Remove unneeded debug output 2014-12-17 14:01:20 -08:00
92bbf27f72 Merge branch 'master' into topdown
Conflicts:
	geojson.c
2014-12-17 11:18:47 -08:00
cba1b8ae7f Remove unneeded old indexing code 2014-12-17 11:16:43 -08:00
0d0a546b1e No need to split features into child tiles when processing the final zoom. 2014-12-17 11:10:46 -08:00
ad17f1f282 Make -Z0 work again for file minimum zoom 2014-12-17 11:05:14 -08:00
3b9f4691c1 Merge pull request #16 from mapbox/nullprop
Allow features to have null properties
2014-12-16 22:49:23 -08:00
a40192bcde Allow features to have null properties 2014-12-16 22:46:00 -08:00
c90ba8511f Fix compiler warning 2014-12-12 14:52:12 -08:00
34a6422c42 Merge branch 'master' into topdown 2014-12-12 11:36:20 -08:00
3f2818a814 More useful progress indicator 2014-12-11 17:59:22 -08:00
c177b8bed2 No need to remove no-ops here unless we clipped 2014-12-11 16:08:53 -08:00
d69431e16b Partition each tile into children instead of repeatedly clipping the parent. 2014-12-11 15:46:54 -08:00
105dfa73d7 Use bounding box for quick accept/reject before detailed clipping. 2014-12-11 13:34:50 -08:00
a867646dfd Don't introduce bogus geometries in clipped-away features. 2014-12-10 12:07:54 -08:00
b068635acf Elevation in geometry is a warning, not an error.
Only complain about the first null geometry, not ever one.
2014-12-09 15:23:22 -08:00
40ecfc0668 Add missing newline to error message. 2014-12-09 15:17:20 -08:00
38a41f4df8 Installation instructions 2014-12-09 11:03:44 -05:00
380550ce85 Remember to remove the closepath after clipping away a polygon 2014-12-04 14:54:11 -08:00
028fef470e Fix the major bug: forgetting to offset back to world coordinates 2014-12-04 14:08:36 -08:00
b7b476b36c Don't introduce bogus geometries in clipped-away features. 2014-12-03 16:30:35 -08:00
08ff40e42f Trying to follow Vlad's suggestion and work from the top down.
Doesn't quite work yet, but seems like the right thing to do.
From the bottom up, indexing is a mess because of the buffering,
and includes unneeded areas in the bounding boxes of big features.
2014-12-03 16:18:43 -08:00
eaeb55bf71 Distinguish between signed and unsigned in the temporary file 2014-12-02 18:03:07 -08:00
28 changed files with 9266 additions and 976 deletions

23
CHANGELOG.md Normal file
View File

@ -0,0 +1,23 @@
## 1.3.0
* Tile generation is multithreaded to take advantage of multiple CPUs
* More compact data representation reduces memory usage and improves speed
* Polygon clipping uses [Clipper](http://www.angusj.com/delphi/clipper/documentation/Docs/_Body.htm)
and makes sure interior and exterior rings are distinguished by winding order
* Individual GeoJSON features can specify their own minzoom and maxzoom
* New `tile-join` utility can add new properties from a CSV file to an existing tileset
* Feature coalescing, line-reversing, and reordering by attribute are now options, not defaults
* Output of `decode` utility is now in GeoJSON format
* Tile generation with a minzoom spends less time on unused lower zoom levels
* Bare geometries without a Feature wrapper are accepted
## 1.2.0
* Switched to top-down rendering, yielding performance improvements
* Add a dot-density gamma feature to thin out especially dense clusters
* Add support for multiple layers, making it possible to include more
than one GeoJSON featurecollection in a map. [#29](https://github.com/mapbox/tippecanoe/pull/29)
* Added flags that let you optionally avoid simplifying lines, restricting
maximum tile sizes, and coalescing features [#30](https://github.com/mapbox/tippecanoe/pull/30)
* Added check that minimum zoom level is less than maximum zoom level
* Added `-v` flag to check tippecanoe's version

19
MADE_WITH.md Normal file
View File

@ -0,0 +1,19 @@
## [Visualizing a Month of Lightning](http://rousseau.io/2015/03/23/visualizing-a-month-of-lightning/) by Jordan Rousseau
![](http://rousseau.io/assets/img/ltg-studio-style.png)
## [Making the most detailed tweet map ever](https://www.mapbox.com/blog/twitter-map-every-tweet/) by Eric Fischer
![](https://farm8.staticflickr.com/7505/15869589271_8a02e84c24_b.jpg)
## [Superpowering Runkeeper's 1.5 million walks, runs, and bike rides](https://www.mapbox.com/blog/runkeeper-million-routes/)
![](https://c1.staticflickr.com/9/8605/15852245980_1ecf0894b8_b.jpg)
## [The Geotaggers' World Atlas](https://www.mapbox.com/blog/geotaggers-world-atlas/) by Eric Fischer
![](http://farm8.staticflickr.com/7634/17040546408_0a14752e6d_b.jpg)
## [Atmospheric River](https://www.mapbox.com/blog/atmospheric-river/)
![](http://farm9.staticflickr.com/8630/16253097589_4dfc706b22_b.jpg)

View File

@ -1,10 +1,17 @@
PREFIX ?= /usr/local
MANDIR ?= /usr/share/man/man1/
all: tippecanoe enumerate decode
all: tippecanoe enumerate decode tile-join
docs: man/tippecanoe.1
install: tippecanoe
mkdir -p $(PREFIX)/bin
cp tippecanoe $(PREFIX)/bin/tippecanoe
cp man/tippecanoe.1 $(MANDIR)
man/tippecanoe.1: README.md
md2man-roff README.md > man/tippecanoe.1
vector_tile.pb.cc vector_tile.pb.h: vector_tile.proto
protoc --cpp_out=. vector_tile.proto
@ -12,12 +19,13 @@ vector_tile.pb.cc vector_tile.pb.h: vector_tile.proto
PG=
H = $(shell find . '(' -name '*.h' -o -name '*.hh' ')')
C = $(shell find . '(' -name '*.c' -o -name '*.cc' ')')
INCLUDES = -I/usr/local/include
LIBS = -L/usr/local/lib
tippecanoe: geojson.o jsonpull.o vector_tile.pb.o tile.o clip.o pool.o mbtiles.o geometry.o projection.o
g++ $(PG) $(LIBS) -O3 -g -Wall -o $@ $^ -lm -lz -lprotobuf-lite -lsqlite3
tippecanoe: geojson.o jsonpull.o vector_tile.pb.o tile.o clip.o pool.o mbtiles.o geometry.o projection.o memfile.o clipper/clipper.o
g++ $(PG) $(LIBS) -O3 -g -Wall -o $@ $^ -lm -lz -lprotobuf-lite -lsqlite3 -lpthread
enumerate: enumerate.o
gcc $(PG) $(LIBS) -O3 -g -Wall -o $@ $^ -lsqlite3
@ -25,6 +33,9 @@ enumerate: enumerate.o
decode: decode.o vector_tile.pb.o projection.o
g++ $(PG) $(LIBS) -O3 -g -Wall -o $@ $^ -lm -lz -lprotobuf-lite -lsqlite3
tile-join: tile-join.o vector_tile.pb.o projection.o pool.o mbtiles.o
g++ $(PG) $(LIBS) -O3 -g -Wall -o $@ $^ -lm -lz -lprotobuf-lite -lsqlite3
libjsonpull.a: jsonpull.o
ar rc $@ $^
ranlib $@
@ -37,3 +48,6 @@ libjsonpull.a: jsonpull.o
clean:
rm tippecanoe *.o
indent:
clang-format -i -style="{BasedOnStyle: Google, IndentWidth: 8, UseTab: Always, AllowShortIfStatementsOnASingleLine: false, ColumnLimit: 0, ContinuationIndentWidth: 8, SpaceAfterCStyleCast: true, IndentCaseLabels: false, AllowShortBlocksOnASingleLine: false, AllowShortFunctionsOnASingleLine: false}" $(C) $(H)

251
README.md
View File

@ -1,14 +1,48 @@
tippecanoe
==========
Build vector tilesets from large collections of GeoJSON features.
Builds [vector tilesets](https://www.mapbox.com/developers/vector-tiles/) from large collections of [GeoJSON](http://geojson.org/)
features. This is a tool for [making maps from huge datasets](MADE_WITH.md).
Intent
------
The goal of Tippecanoe is to enable making a scale-independent view of your data,
so that at any level from the entire world to a single building, you can see
the density and texture of the data rather than a simplification from dropping
supposedly unimportant features or clustering or aggregating them.
If you give it all of OpenStreetMap and zoom out, it should give you back
something that looks like "[All Streets](http://benfry.com/allstreets/map5.html)"
rather than something that looks like an Interstate road atlas.
If you give it all the building footprints in Los Angeles and zoom out
far enough that most individual buildings are no longer discernable, you
should still be able to see the extent and variety of development in every neighborhood,
not just the largest downtown buildings.
If you give it a collection of years of tweet locations, you should be able to
see the shape and relative popularity of every point of interest and every
significant travel corridor.
Installation
------------
The easiest way to install tippecanoe on OSX is with [Homebrew](http://brew.sh/):
```js
$ brew install tippecanoe
```
Usage
-----
tippecanoe -o file.mbtiles [file.json]
```sh
$ tippecanoe -o file.mbtiles [file.json ...]
```
If the file is not specified, it reads GeoJSON from the standard input.
If no files are specified, it reads GeoJSON from the standard input.
If multiple files are specified, each is placed in its own layer.
The GeoJSON features need not be wrapped in a FeatureCollection.
You can concatenate multiple GeoJSON features or files together,
@ -18,25 +52,113 @@ it encounters.
Options
-------
* -l <i>name</i>: Layer name (default "file" if source is file.json)
* -n <i>name</i>: Human-readable name (default file.json)
* -z <i>zoom</i>: Base zoom level (default 14)
* -Z <i>zoom</i>: Lowest zoom level (default 0)
* -d <i>detail</i>: Detail at base zoom level (default 26-basezoom, ~0.5m, for tile resolution of 4096 if -z14)
* -D <i>detail</i>: Detail at lower zoom levels (default 10, for tile resolution of 1024)
* -x <i>name</i>: Exclude the named properties from all features
* -y <i>name</i>: Include the named properties in all features, excluding all those not explicitly named
* -X: Exclude all properties and encode only geometries
### Naming
* -l _name_: Layer name (default "file" if source is file.json or output is file.mbtiles). Only works if there is only one layer.
* -n _name_: Human-readable name (default file.json)
### File control
* -o _file_.mbtiles: Name the output file.
* -f: Delete the mbtiles file if it already exists instead of giving an error
* -r <i>rate</i>: Rate at which dots are dropped at lower zoom levels (default 2.5)
* -b <i>pixels</i>: Buffer size where features are duplicated from adjacent tiles (default 5)
* -t _directory_: Put the temporary files in _directory_.
### Zoom levels and resolution
* -z _zoom_: Base (maxzoom) zoom level (default 14)
* -Z _zoom_: Lowest (minzoom) zoom level (default 0)
* -d _detail_: Detail at base zoom level (default 12 at -z14 or higher, or 13 at -z13 or lower. Detail beyond 13 has rendering problems with Mapbox GL.)
* -D _detail_: Detail at lower zoom levels (default 10, for tile resolution of 1024)
* -m _detail_: Minimum detail that it will try if tiles are too big at regular detail (default 7)
* -b _pixels_: Buffer size where features are duplicated from adjacent tiles. Units are "screen pixels"--1/256th of the tile width or height. (default 5)
### Properties
* -x _name_: Exclude the named properties from all features
* -y _name_: Include the named properties in all features, excluding all those not explicitly named
* -X: Exclude all properties and encode only geometries
### Point simplification
* -r _rate_: Rate at which dots are dropped at lower zoom levels (default 2.5)
* -g _gamma_: Rate at which especially dense dots are dropped (default 0, for no effect). A gamma of 2 reduces the number of dots less than a pixel apart to the square root of their original number.
### Doing more
* -ac: Coalesce adjacent line and polygon features that have the same properties
* -ar: Try reversing the directions of lines to make them coalesce and compress better
* -ao: Reorder features to put ones with the same properties in sequence, to try to get them to coalesce
* -al: Let "dot" dropping at lower zooms apply to lines too
### Doing less
* -ps: Don't simplify lines
* -pf: Don't limit tiles to 200,000 features
* -pk: Don't limit tiles to 500K bytes
* -pd: Dynamically drop some fraction of features from large tiles to keep them under the 500K size limit. It will probably look ugly at the tile boundaries.
* -pi: Preserve the original input order of features as the drawing order instead of ordering geographically. (This is implemented as a restoration of the original order at the end, so that dot-dropping is still geographic, which means it also undoes -ao).
* -q: Work quietly instead of reporting progress
Example
-------
tippecanoe -o alameda.mbtiles -l alameda -n "Alameda County from TIGER" -z13 tl_2014_06001_roads.json
```sh
$ tippecanoe -o alameda.mbtiles -l alameda -n "Alameda County from TIGER" -z13 tl_2014_06001_roads.json
```
cat tiger/tl_2014_*_roads.json | tippecanoe -o tiger.mbtiles -l roads -n "All TIGER roads, one zoom" -z12 -Z12 -d14 -x LINEARID -x RTTYP
```
$ cat tiger/tl_2014_*_roads.json | tippecanoe -o tiger.mbtiles -l roads -n "All TIGER roads, one zoom" -z12 -Z12 -d14 -x LINEARID -x RTTYP
```
GeoJSON extension
-----------------
Tippecanoe defines a GeoJSON extension that you can use to specify the minimum and/or maximum zoom level
at which an individual feature will be included in the vector tile dataset being produced.
If you have a feature like this:
```
{
"type" : "Feature",
"tippecanoe" : { "maxzoom" : 9, "minzoom" : 4 },
"properties" : { "FULLNAME" : "N Vasco Rd" },
"geometry" : {
"type" : "LineString",
"coordinates" : [ [ -121.733350, 37.767671 ], [ -121.733600, 37.767483 ], [ -121.733131, 37.766952 ] ]
}
}
```
with a `tippecanoe` object specifiying a `maxzoom` of 9 and a `minzoom` of 4, the feature
will only appear in the vector tiles for zoom levels 4 through 9. Note that the `tippecanoe`
object belongs to the Feature, not to its `properties`.
Point styling
-------------
To provide a consistent density gradient as you zoom, the Mapbox Studio style needs to be
coordinated with the base zoom level and dot-dropping rate. You can use this shell script to
calculate the appropriate marker-width at high zoom levels to match the fraction of dots
that were dropped at low zoom levels.
If you used `-z` to change the base zoom level or `-r` to change the
dot-dropping rate, replace them in the `basezoom` and `rate` below.
awk 'BEGIN {
dotsize = 2; # up to you to decide
basezoom = 14; # tippecanoe -z 14
rate = 2.5; # tippecanoe -r 2.5
print " marker-line-width: 0;";
print " marker-ignore-placement: true;";
print " marker-allow-overlap: true;";
print " marker-width: " dotsize ";";
for (i = basezoom + 1; i <= 22; i++) {
print " [zoom >= " i "] { marker-width: " (dotsize * exp(log(sqrt(rate)) * (i - basezoom))) "; }";
}
exit(0);
}'
Geometric simplifications
-------------------------
@ -48,11 +170,15 @@ For point features, it drops 1/2.5 of the dots for each zoom level above the bas
I don't know why 2.5 is the appropriate number, but the densities of many different
data sets fall off at about this same rate. You can use -r to specify a different rate.
You can use the gamma option to thin out especially dense clusters of points.
For any area where dots are closer than one pixel together (at whatever zoom level),
a gamma of 3, for example, will reduce these clusters to the cube root of their original density.
For line features, it drops any features that are too small to draw at all.
This still leaves the lower zooms too dark (and too dense for the 500K tile limit,
in some places), so I need to figure out an equitable way to throw features away.
Any polygons that are smaller than a minimum area (currently 9 square subpixels) will
Any polygons that are smaller than a minimum area (currently 4 square subpixels) will
have their probability diffused, so that some of them will be drawn as a square of
this minimum size and others will not be drawn at all, preserving the total area that
all of them should have had together.
@ -67,11 +193,98 @@ lower resolutions before failing if it still doesn't fit.
Development
-----------
Requires protoc (brew install protobuf or apt-get install libprotobuf-dev),
and sqlite3 (apt-get install libsqlite3-dev). To build:
Requires protoc and sqlite3. Rebuilding the manpage
uses md2man (`gem install md2man`).
MacOS:
brew install protobuf
Linux:
sudo apt-get install libprotobuf-dev
sudo apt-get install protobuf-compiler
sudo apt-get install libsqlite3-dev
Then build:
make
and perhaps
make install
Examples
------
Check out [some examples of maps made with tippecanoe](MADE_WITH.md)
Name
----
The name is [a joking reference](http://en.wikipedia.org/wiki/Tippecanoe_and_Tyler_Too) to a "tiler" for making map tiles.
tile-join
=========
Tile-join is a tool for joining new attributes from a CSV file to features that
have already been tiled with tippecanoe. It reads the tiles from an existing .mbtiles
file, matches them against the records of the CSV, and writes out a new tileset.
The options are:
* -o *out.mbtiles*: Write the new tiles to the specified .mbtiles file
* -f: Remove *out.mbtiles* if it already exists
* -c *match.csv*: Use *match.csv* as the source for new attributes to join to the features. The first line of the file should be the key names; the other lines are values. The first column is the one to match against the existing features; the other columns are the new data to add.
* -x *key*: Remove attributes of type *key* from the output. You can use this to remove the field you are matching against if you no longer need it after joining, or to remove any other attributes you don't want.
* -i: Only include features that matched the CSV.
Because tile-join just copies the geometries to the new .mbtiles without processing them,
it doesn't have any of tippecanoe's recourses if the new tiles are bigger than the 500K tile limit.
If a tile is too big, it is just left out of the new tileset.
Example
-------
Imagine you have a tileset of census blocks:
```sh
curl -O http://www2.census.gov/geo/tiger/TIGER2010/TABBLOCK/2010/tl_2010_06001_tabblock10.zip
unzip tl_2010_06001_tabblock10.zip
ogr2ogr -f GeoJSON tl_2010_06001_tabblock10.json tl_2010_06001_tabblock10.shp
./tippecanoe -o tl_2010_06001_tabblock10.mbtiles tl_2010_06001_tabblock10.json
```
and a CSV of their populations:
```sh
curl -O http://www2.census.gov/census_2010/01-Redistricting_File--PL_94-171/California/ca2010.pl.zip
unzip -p ca2010.pl.zip cageo2010.pl |
awk 'BEGIN {
print "GEOID10,population"
}
(substr($0, 9, 3) == "750") {
print "\"" substr($0, 28, 2) substr($0, 30, 3) substr($0, 55, 6) substr($0, 62, 4) "\"," (0 + substr($0, 328, 9))
}' > population.csv
```
which looks like this:
```
GEOID10,population
"060014277003018",0
"060014283014046",0
"060014284001020",0
...
"060014507501001",202
"060014507501002",119
"060014507501003",193
"060014507501004",85
...
```
Then you can join those populations to the geometries and discard the no-longer-needed ID field:
```sh
./tile-join -o population.mbtiles -x GEOID10 -c population.csv tl_2010_06001_tabblock10.mbtiles
```

18
clip.c
View File

@ -29,37 +29,37 @@ int clip(double *x0, double *y0, double *x1, double *y1, double xmin, double ymi
int outcode1 = computeOutCode(*x1, *y1, xmin, ymin, xmax, ymax);
int accept = 0;
int changed = 0;
while (1) {
if (!(outcode0 | outcode1)) { // Bitwise OR is 0. Trivially accept and get out of loop
if (!(outcode0 | outcode1)) { // Bitwise OR is 0. Trivially accept and get out of loop
accept = 1;
break;
} else if (outcode0 & outcode1) { // Bitwise AND is not 0. Trivially reject and get out of loop
} else if (outcode0 & outcode1) { // Bitwise AND is not 0. Trivially reject and get out of loop
break;
} else {
// failed both tests, so calculate the line segment to clip
// from an outside point to an intersection with clip edge
double x = *x0, y = *y0;
// At least one endpoint is outside the clip rectangle; pick it.
int outcodeOut = outcode0 ? outcode0 : outcode1;
// Now find the intersection point;
// use formulas y = y0 + slope * (x - x0), x = x0 + (1 / slope) * (y - y0)
if (outcodeOut & TOP) { // point is above the clip rectangle
if (outcodeOut & TOP) { // point is above the clip rectangle
x = *x0 + (*x1 - *x0) * (ymax - *y0) / (*y1 - *y0);
y = ymax;
} else if (outcodeOut & BOTTOM) { // point is below the clip rectangle
} else if (outcodeOut & BOTTOM) { // point is below the clip rectangle
x = *x0 + (*x1 - *x0) * (ymin - *y0) / (*y1 - *y0);
y = ymin;
} else if (outcodeOut & RIGHT) { // point is to the right of clip rectangle
y = *y0 + (*y1 - *y0) * (xmax - *x0) / (*x1 - *x0);
x = xmax;
} else if (outcodeOut & LEFT) { // point is to the left of clip rectangle
} else if (outcodeOut & LEFT) { // point is to the left of clip rectangle
y = *y0 + (*y1 - *y0) * (xmin - *x0) / (*x1 - *x0);
x = xmin;
}
// Now we move outside point to intersection point to clip
// and get ready for next pass.
if (outcodeOut == outcode0) {

24
clipper/License.txt Normal file
View File

@ -0,0 +1,24 @@
Boost Software License - Version 1.0 - August 17th, 2003
http://www.boost.org/LICENSE_1_0.txt
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.

407
clipper/README Normal file
View File

@ -0,0 +1,407 @@
=====================================================================
Clipper Change Log
=====================================================================
v6.2.1 (31 October 2014) Rev 482
* Bugfix in ClipperOffset.Execute where the Polytree.IsHole property
was returning incorrect values with negative offsets
* Very minor improvement to join rounding in ClipperOffset
* Fixed CPP OpenGL demo.
v6.2.0 (17 October 2014) Rev 477
* Numerous minor bugfixes, too many to list.
(See revisions 454-475 in Sourceforge Repository)
* The ZFillFunction (custom callback function) has had its parameters
changed.
* Curves demo removed (temporarily).
* Deprecated functions have been removed.
v6.1.5 (26 February 2014) Rev 460
* Improved the joining of output polygons sharing a common edge
when those common edges are horizontal.
* Fixed a bug in ClipperOffset.AddPath() which would produce
incorrect solutions when open paths were added before closed paths.
* Minor code tidy and performance improvement
v6.1.4 (6 February 2014)
* Fixed bugs in MinkowskiSum
* Fixed minor bug when using Clipper.ForceSimplify.
* Modified use_xyz callback so that all 4 vertices around an
intersection point are now passed to the callback function.
v6.1.3a (22 January 2014) Rev 453
* Fixed buggy PointInPolygon function (C++ and C# only).
Note this bug only affected the newly exported function, the
internal PointInPolygon function used by Clipper was OK.
v6.1.3 (19 January 2014) Rev 452
* Fixed potential endless loop condition when adding open
paths to Clipper.
* Fixed missing implementation of SimplifyPolygon function
in C++ code.
* Fixed incorrect upper range constant for polygon coordinates
in Delphi code.
* Added PointInPolygon function.
* Overloaded MinkowskiSum function to accommodate multi-contour
paths.
v6.1.2 (15 December 2013) Rev 444
* Fixed broken C++ header file.
* Minor improvement to joining polygons.
v6.1.1 (13 December 2013) Rev 441
* Fixed a couple of bugs affecting open paths that could
raise unhandled exceptions.
v6.1.0 (12 December 2013)
* Deleted: Previously deprecated code has been removed.
* Modified: The OffsetPaths function is now deprecated as it has
been replaced by the ClipperOffset class which is much more
flexible.
* Bugfixes: Several minor bugs have been fixed including
occasionally an incorrect nesting within the PolyTree structure.
v6.0.0 (30 October 2013)
* Added: Open path (polyline) clipping. A new 'Curves' demo
application showcases this (see the 'Curves' directory).
* Update: Major improvement in the merging of
shared/collinear edges in clip solutions (see Execute).
* Added: The IntPoint structure now has an optional 'Z' member.
(See the precompiler directive use_xyz.)
* Added: Users can now force Clipper to use 32bit integers
(via the precompiler directive use_int32) instead of using
64bit integers.
* Modified: To accommodate open paths, the Polygon and Polygons
structures have been renamed Path and Paths respectively. The
AddPolygon and AddPolygons methods of the ClipperBase class
have been renamed AddPath and AddPaths respectively. Several
other functions have been similarly renamed.
* Modified: The PolyNode Class has a new IsOpen property.
* Modified: The Clipper class has a new ZFillFunction property.
* Added: MinkowskiSum and MinkowskiDiff functions added.
* Added: Several other new functions have been added including
PolyTreeToPaths, OpenPathsFromPolyTree and ClosedPathsFromPolyTree.
* Added: The Clipper constructor now accepts an optional InitOptions
parameter to simplify setting properties.
* Bugfixes: Numerous minor bugs have been fixed.
* Deprecated: Version 6 is a major upgrade from previous versions
and quite a number of changes have been made to exposed structures
and functions. To minimize inconvenience to existing library users,
some code has been retained and some added to maintain backward
compatibility. However, because this code will be removed in a
future update, it has been marked as deprecated and a precompiler
directive use_deprecated has been defined.
v5.1.6 (23 May 2013)
* BugFix: CleanPolygon function was buggy.
* Changed: The behaviour of the 'miter' JoinType has been
changed so that when squaring occurs, it's no longer
extended up to the miter limit but is squared off at
exactly 'delta' units. (This improves the look of mitering
with larger limits at acute angles.)
* Added: New OffsetPolyLines function
* Update: Minor code refactoring and optimisations
v5.1.5 (5 May 2013)
* Added: ForceSimple property to Clipper class
* Update: Improved documentation
v5.1.4 (24 March 2013)
* Update: CleanPolygon function enhanced.
* Update: Documentation improved.
v5.1.3 (14 March 2013)
* Bugfix: Minor bugfixes.
* Update: Documentation significantly improved.
v5.1.2 (26 February 2013)
* Bugfix: PolyNode class was missing a constructor.
* Update: The MiterLimit parameter in the OffsetPolygons
function has been renamed Limit and can now also be used to
limit the number of vertices used to construct arcs when
JoinType is set to jtRound.
v5.1.0 (17 February 2013)
* Update: ExPolygons has been replaced with the PolyTree &
PolyNode classes to more fully represent the parent-child
relationships of the polygons returned by Clipper.
* Added: New CleanPolygon and CleanPolygons functions.
* Bugfix: Another orientation bug fixed.
v5.0.2 - 30 December 2012
* Bugfix: Significant fixes in and tidy of the internal
Int128 class (which is used only when polygon coordinate
values are greater than ±0x3FFFFFFF (~1.07e9)).
* Update: The Area algorithm has been updated and is faster.
* Update: Documentation updates. The newish but undocumented
'CheckInputs' parameter of the OffsetPolygons function has been
renamed 'AutoFix' and documented too. The comments on rounding
have also been improved (ie clearer and expanded).
v4.10.0 - 25 December 2012
* Bugfix: Orientation bugs should now be resolved (finally!).
* Bugfix: Bug in Int128 class
v4.9.8 - 2 December 2012
* Bugfix: Further fixes to rare Orientation bug.
v4.9.7 - 29 November 2012
* Bugfix: Bug that very rarely returned the wrong polygon
orientation.
* Bugfix: Obscure bug affecting OffsetPolygons when using
jtRound for the JoinType parameter and when polygons also
contain very large coordinate values (> +/-100000000000).
v4.9.6 - 9 November 2012
* Bugfix: Another obscure bug related to joining polygons.
v4.9.4 - 2 November 2012
* Bugfix: Bugs in Int128 class occasionally causing
wrong orientations.
* Bugfix: Further fixes related to joining polygons.
v4.9.0 - 9 October 2012
* Bugfix: Obscure bug related to joining polygons.
v4.8.9 - 25 September 2012
* Bugfix: Obscure bug related to precision of intersections.
v4.8.8 - 30 August 2012
* Bugfix: Fixed bug in OffsetPolygons function introduced in
version 4.8.5.
v4.8.7 - 24 August 2012
* Bugfix: ReversePolygon function in C++ translation was broken.
* Bugfix: Two obscure bugs affecting orientation fixed too.
v4.8.6 - 11 August 2012
* Bugfix: Potential for memory overflow errors when using
ExPolygons structure.
* Bugfix: The polygon coordinate range has been reduced to
+/- 0x3FFFFFFFFFFFFFFF (4.6e18).
* Update: ReversePolygons function was misnamed ReversePoints in C++.
* Update: SimplifyPolygon function now takes a PolyFillType parameter.
v4.8.5 - 15 July 2012
* Bugfix: Potential for memory overflow errors in OffsetPolygons().
v4.8.4 - 1 June 2012
* Bugfix: Another obscure bug affecting ExPolygons structure.
v4.8.3 - 27 May 2012
* Bugfix: Obscure bug causing incorrect removal of a vertex.
v4.8.2 - 21 May 2012
* Bugfix: Obscure bug could cause an exception when using
ExPolygon structure.
v4.8.1 - 12 May 2012
* Update: Cody tidy and minor bug fixes.
v4.8.0 - 30 April 2012
* Bugfix: Occasional errors in orientation fixed.
* Update: Added notes on rounding to the documentation.
v4.7.6 - 11 April 2012
* Fixed a bug in Orientation function (affecting C# translations only).
* Minor documentation update.
v4.7.5 - 28 March 2012
* Bugfix: Fixed a recently introduced bug that occasionally caused an
unhandled exception in C++ and C# translations.
v4.7.4 - 15 March 2012
* Bugfix: Another minor bugfix.
v4.7.2 - 4 March 2012
* Bugfix: Fixed bug introduced in ver 4.7 which sometimes caused
an exception if ExPolygon structure was passed to Clipper's
Execute method.
v4.7.1 - 3 March 2012
* Bugfix: Rare crash when JoinCommonEdges joined polygons that
'cancelled' each other.
* Bugfix: Clipper's internal Orientation method occasionally
returned wrong result.
* Update: Improved C# code (thanks to numerous excellent suggestions
from David Piepgrass)
v4.7 - 10 February 2012
* Improved the joining of output polygons sharing a common edge.
v4.6.6 - 3 February 2012
* Bugfix: Another obscure bug occasionally causing incorrect
polygon orientation.
v4.6.5 - 17 January 2012
* Bugfix: Obscure bug occasionally causing incorrect hole
assignment in ExPolygon structure.
v4.6.4 - 8 November 2011
* Added: SimplifyPolygon and SimplifyPolygons functions.
v4.6.3 - 11 November 2011
* Bugfix: Fixed another minor mitering bug in OffsetPolygons.
v4.6.2 - 10 November 2011
* Bugfix: Fixed a rare bug in the orientation of polygons
returned by Clipper's Execute() method.
* Bugfix: Previous update introduced a mitering bug in the
OffsetPolygons function.
v4.6 - 29 October 2011
* Added: Support for Positive and Negative polygon fill
types (in addition to the EvenOdd and NonZero fill types).
* Bugfix: The OffsetPolygons function was generating the
occasional artefact when 'shrinking' polygons.
v4.5.5 - 8 October 2011
* Bugfix: Fixed an obscure bug in Clipper's JoinCommonEdges
method.
* Update: Replaced IsClockwise function with Orientation
function. The orientation issues affecting OffsetPolygons
should now be finally resolved.
* Change: The Area function once again returns a signed value.
v4.5.1 - 28 September 2011
* Deleted: The UseFullCoordinateRange property has been
deleted since integer range is now managed implicitly.
* BugFix: Minor bug in OffsetPolygon mitering.
* Change: C# JoinType enum moved from Clipper class to
ClipperLib namespace.
* Change: The Area function now returns the absolute area
(irrespective of orientation).
* Change: The IsClockwise function now requires a second
parameter - YAxisPositiveUpward - to accommodate displays
with Y-axis oriented in either direction
v4.4.4 - 10 September 2011
* Change: Deleted jtButt from JoinType (used by the
OffsetPolygons function).
* BugFix: Fixed another minor bug in OffsetPolygons function.
* Update: Further improvements to the help file
v4.4.3 - 29 August 2011
* BugFix: fixed a minor rounding issue in OffsetPolygons
function (affected C++ & C# translations).
* BugFix: fixed a minor bug in OffsetPolygons' function
declaration (affected C++ translation only).
* Change: 'clipper' namespace changed to 'ClipperLib'
namespace in both C++ and C# code to remove the ambiguity
between the Clipper class and the namespace. (This also
required numerous updates to the accompanying demos.)
v4.4.2 - 26 August 2011
* BugFix: minor bugfixes in Clipper.
* Update: the OffsetPolygons function has been significantly
improved by offering 4 different join styles.
v4.4.0 - 6 August 2011
* BugFix: A number of minor bugs have been fixed that mostly
affected the new ExPolygons structure.
v4.3.0 - 17 June 2011
* New: ExPolygons structure that explicitly associates 'hole'
polygons with their 'outer' container polygons.
* New: Execute method overloaded so the solution parameter
can now be either Polygons or ExPolygons.
* BugFix: Fixed a rare bug in solution polygons orientation.
v4.2.8 - 21 May 2011
* Update: JoinCommonEdges() improved once more.
* BugFix: Several minor bugs fixed.
v4.2.6 - 1 May 2011
* Bugfix: minor bug in SlopesEqual function.
* Update: Merging of output polygons sharing common edges
has been significantly inproved
v4.2.4 - 26 April 2011
Input polygon coordinates can now contain the full range of
signed 64bit integers (ie +/-9,223,372,036,854,775,807). This
means that floating point values can be converted to and from
Clipper's 64bit integer coordinates structure (IntPoint) and
still retain a precision of up to 18 decimal places. However,
since the large-integer math that supports this expanded range
imposes a small cost on performance (~15%), a new property
UseFullCoordinateRange has been added to the Clipper class to
allow users the choice of whether or not to use this expanded
coordinate range. If this property is disabled, coordinate values
are restricted to +/-1,500,000,000.
v4.2 - 12 April 2011
JoinCommonEdges() code significantly improved plus other minor
improvements.
v4.1.2 - 9 April 2011
* Update: Minor code tidy.
* Bugfix: Possible endless loop in JoinCommonEdges() in clipper.pas.
v4.1.1 - 8 April 2011
* Update: All polygon coordinates are now stored as 64bit integers
(though they're still restricted to range -1.5e9 to +1.5e9 pending
the inclusion of code supporting 64bit math).
* Change: AddPolygon and AddPolygons methods now return boolean
values.
* Bugfix: Bug in JoinCommonEdges() caused potential endless loop.
* Bugfix: Bug in IsClockwise(). (C++ code only)
v4.0 - 5 April 2011
* Clipper 4 is a major rewrite of earlier versions. The biggest
change is that floating point values are no longer used,
except for the storing of edge slope values. The main benefit
of this is the issue of numerical robustness has been
addressed. Due to other major code improvements Clipper v4
is approximately 40% faster than Clipper v3.
* The AddPolyPolygon method has been renamed to AddPolygons.
* The IgnoreOrientation property has been removed.
* The clipper_misc library has been merged back into the
main clipper library.
v3.1.0 - 17 February 2011
* Bugfix: Obscure bug in TClipperBase.SetDx method that caused
problems with very small edges ( edges <1/1000th pixel in size).
v3.0.3 - 9 February 2011
* Bugfix: Significant bug, but only in C# code.
* Update: Minor refactoring.
v3.0 - 31 January 2011
* Update: Major rewrite of the portion of code that calculates
the output polygons' orientation.
* Update: Help file significantly improved.
* Change: Renamed ForceOrientation property to IgnoreOrientation.
If the orientation of output polygons is not important, or can
be managed separately, clipping routines can be sped up by about
60% by setting IgnoreOrientation to true. Defaults to false.
* Change: The OffsetPolygon and Area functions have been moved to
the new unit - clipper_misc.
2.99 - 15 January 2011
* Bugfix: Obscure bug in AddPolygon method could cause an endless loop.
2.8 - 20 November 2010
* Updated: Output polygons which previously shared a common
edge are now merged.
* Changed: The orientation of outer polygons is now clockwise
when the display's Y axis is positive downwards (as is
typical for most Windows applications). Inner polygons
(holes) have the opposite orientation.
* Added: Support module for Cairo Graphics Library (with demo).
* Updated: C# and C++ demos.
2.522 - 15 October 2010
* Added C# translation (thanks to Olivier Lejeune) and
a link to Ruby bindings (thanks to Mike Owens).
2.0 - 30 July 2010
* Clipper now clips using both the Even-Odd (alternate) and
Non-Zero (winding) polygon filling rules. (Previously Clipper
assumed the Even-Odd rule for polygon filling.)
1.4c - 16 June 2010
* Added C++ support for AGG graphics library
1.2s - 2 June 2010
* Added C++ translation of clipper.pas
1.0 - 9 May 2010

4464
clipper/clipper.cpp Normal file

File diff suppressed because it is too large Load Diff

395
clipper/clipper.hpp Normal file
View File

@ -0,0 +1,395 @@
/*******************************************************************************
* *
* Author : Angus Johnson *
* Version : 6.2.1 *
* Date : 31 October 2014 *
* Website : http://www.angusj.com *
* Copyright : Angus Johnson 2010-2014 *
* *
* License: *
* Use, modification & distribution is subject to Boost Software License Ver 1. *
* http://www.boost.org/LICENSE_1_0.txt *
* *
* Attributions: *
* The code in this library is an extension of Bala Vatti's clipping algorithm: *
* "A generic solution to polygon clipping" *
* Communications of the ACM, Vol 35, Issue 7 (July 1992) pp 56-63. *
* http://portal.acm.org/citation.cfm?id=129906 *
* *
* Computer graphics and geometric modeling: implementation and algorithms *
* By Max K. Agoston *
* Springer; 1 edition (January 4, 2005) *
* http://books.google.com/books?q=vatti+clipping+agoston *
* *
* See also: *
* "Polygon Offsetting by Computing Winding Numbers" *
* Paper no. DETC2005-85513 pp. 565-575 *
* ASME 2005 International Design Engineering Technical Conferences *
* and Computers and Information in Engineering Conference (IDETC/CIE2005) *
* September 24-28, 2005 , Long Beach, California, USA *
* http://www.me.berkeley.edu/~mcmains/pubs/DAC05OffsetPolygon.pdf *
* *
*******************************************************************************/
#ifndef clipper_hpp
#define clipper_hpp
#define CLIPPER_VERSION "6.2.0"
//use_int32: When enabled 32bit ints are used instead of 64bit ints. This
//improve performance but coordinate values are limited to the range +/- 46340
//#define use_int32
//use_xyz: adds a Z member to IntPoint. Adds a minor cost to perfomance.
//#define use_xyz
//use_lines: Enables line clipping. Adds a very minor cost to performance.
//#define use_lines
//use_deprecated: Enables temporary support for the obsolete functions
//#define use_deprecated
#include <vector>
#include <set>
#include <stdexcept>
#include <cstring>
#include <cstdlib>
#include <ostream>
#include <functional>
#include <queue>
namespace ClipperLib {
enum ClipType { ctIntersection, ctUnion, ctDifference, ctXor };
enum PolyType { ptSubject, ptClip };
//By far the most widely used winding rules for polygon filling are
//EvenOdd & NonZero (GDI, GDI+, XLib, OpenGL, Cairo, AGG, Quartz, SVG, Gr32)
//Others rules include Positive, Negative and ABS_GTR_EQ_TWO (only in OpenGL)
//see http://glprogramming.com/red/chapter11.html
enum PolyFillType { pftEvenOdd, pftNonZero, pftPositive, pftNegative };
#ifdef use_int32
typedef int cInt;
static cInt const loRange = 0x7FFF;
static cInt const hiRange = 0x7FFF;
#else
typedef signed long long cInt;
static cInt const loRange = 0x3FFFFFFF;
static cInt const hiRange = 0x3FFFFFFFFFFFFFFFLL;
typedef signed long long long64; //used by Int128 class
typedef unsigned long long ulong64;
#endif
struct IntPoint {
cInt X;
cInt Y;
#ifdef use_xyz
cInt Z;
IntPoint(cInt x = 0, cInt y = 0, cInt z = 0): X(x), Y(y), Z(z) {};
#else
IntPoint(cInt x = 0, cInt y = 0): X(x), Y(y) {};
#endif
friend inline bool operator== (const IntPoint& a, const IntPoint& b)
{
return a.X == b.X && a.Y == b.Y;
}
friend inline bool operator!= (const IntPoint& a, const IntPoint& b)
{
return a.X != b.X || a.Y != b.Y;
}
};
//------------------------------------------------------------------------------
typedef std::vector< IntPoint > Path;
typedef std::vector< Path > Paths;
inline Path& operator <<(Path& poly, const IntPoint& p) {poly.push_back(p); return poly;}
inline Paths& operator <<(Paths& polys, const Path& p) {polys.push_back(p); return polys;}
std::ostream& operator <<(std::ostream &s, const IntPoint &p);
std::ostream& operator <<(std::ostream &s, const Path &p);
std::ostream& operator <<(std::ostream &s, const Paths &p);
struct DoublePoint
{
double X;
double Y;
DoublePoint(double x = 0, double y = 0) : X(x), Y(y) {}
DoublePoint(IntPoint ip) : X((double)ip.X), Y((double)ip.Y) {}
};
//------------------------------------------------------------------------------
#ifdef use_xyz
typedef void (*ZFillCallback)(IntPoint& e1bot, IntPoint& e1top, IntPoint& e2bot, IntPoint& e2top, IntPoint& pt);
#endif
enum InitOptions {ioReverseSolution = 1, ioStrictlySimple = 2, ioPreserveCollinear = 4};
enum JoinType {jtSquare, jtRound, jtMiter};
enum EndType {etClosedPolygon, etClosedLine, etOpenButt, etOpenSquare, etOpenRound};
class PolyNode;
typedef std::vector< PolyNode* > PolyNodes;
class PolyNode
{
public:
PolyNode();
virtual ~PolyNode(){};
Path Contour;
PolyNodes Childs;
PolyNode* Parent;
PolyNode* GetNext() const;
bool IsHole() const;
bool IsOpen() const;
int ChildCount() const;
private:
unsigned Index; //node index in Parent.Childs
bool m_IsOpen;
JoinType m_jointype;
EndType m_endtype;
PolyNode* GetNextSiblingUp() const;
void AddChild(PolyNode& child);
friend class Clipper; //to access Index
friend class ClipperOffset;
};
class PolyTree: public PolyNode
{
public:
~PolyTree(){Clear();};
PolyNode* GetFirst() const;
void Clear();
int Total() const;
private:
PolyNodes AllNodes;
friend class Clipper; //to access AllNodes
};
bool Orientation(const Path &poly);
double Area(const Path &poly);
int PointInPolygon(const IntPoint &pt, const Path &path);
void SimplifyPolygon(const Path &in_poly, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
void SimplifyPolygons(const Paths &in_polys, Paths &out_polys, PolyFillType fillType = pftEvenOdd);
void SimplifyPolygons(Paths &polys, PolyFillType fillType = pftEvenOdd);
void CleanPolygon(const Path& in_poly, Path& out_poly, double distance = 1.415);
void CleanPolygon(Path& poly, double distance = 1.415);
void CleanPolygons(const Paths& in_polys, Paths& out_polys, double distance = 1.415);
void CleanPolygons(Paths& polys, double distance = 1.415);
void MinkowskiSum(const Path& pattern, const Path& path, Paths& solution, bool pathIsClosed);
void MinkowskiSum(const Path& pattern, const Paths& paths, Paths& solution, bool pathIsClosed);
void MinkowskiDiff(const Path& poly1, const Path& poly2, Paths& solution);
void PolyTreeToPaths(const PolyTree& polytree, Paths& paths);
void ClosedPathsFromPolyTree(const PolyTree& polytree, Paths& paths);
void OpenPathsFromPolyTree(PolyTree& polytree, Paths& paths);
void ReversePath(Path& p);
void ReversePaths(Paths& p);
struct IntRect { cInt left; cInt top; cInt right; cInt bottom; };
//enums that are used internally ...
enum EdgeSide { esLeft = 1, esRight = 2};
//forward declarations (for stuff used internally) ...
struct TEdge;
struct IntersectNode;
struct LocalMinimum;
struct Scanbeam;
struct OutPt;
struct OutRec;
struct Join;
typedef std::vector < OutRec* > PolyOutList;
typedef std::vector < TEdge* > EdgeList;
typedef std::vector < Join* > JoinList;
typedef std::vector < IntersectNode* > IntersectList;
//------------------------------------------------------------------------------
//ClipperBase is the ancestor to the Clipper class. It should not be
//instantiated directly. This class simply abstracts the conversion of sets of
//polygon coordinates into edge objects that are stored in a LocalMinima list.
class ClipperBase
{
public:
ClipperBase();
virtual ~ClipperBase();
bool AddPath(const Path &pg, PolyType PolyTyp, bool Closed);
bool AddPaths(const Paths &ppg, PolyType PolyTyp, bool Closed);
virtual void Clear();
IntRect GetBounds();
bool PreserveCollinear() {return m_PreserveCollinear;};
void PreserveCollinear(bool value) {m_PreserveCollinear = value;};
protected:
void DisposeLocalMinimaList();
TEdge* AddBoundsToLML(TEdge *e, bool IsClosed);
void PopLocalMinima();
virtual void Reset();
TEdge* ProcessBound(TEdge* E, bool IsClockwise);
void DoMinimaLML(TEdge* E1, TEdge* E2, bool IsClosed);
TEdge* DescendToMin(TEdge *&E);
void AscendToMax(TEdge *&E, bool Appending, bool IsClosed);
typedef std::vector<LocalMinimum> MinimaList;
MinimaList::iterator m_CurrentLM;
MinimaList m_MinimaList;
bool m_UseFullRange;
EdgeList m_edges;
bool m_PreserveCollinear;
bool m_HasOpenPaths;
};
//------------------------------------------------------------------------------
class Clipper : public virtual ClipperBase
{
public:
Clipper(int initOptions = 0);
~Clipper();
bool Execute(ClipType clipType,
Paths &solution,
PolyFillType subjFillType = pftEvenOdd,
PolyFillType clipFillType = pftEvenOdd);
bool Execute(ClipType clipType,
PolyTree &polytree,
PolyFillType subjFillType = pftEvenOdd,
PolyFillType clipFillType = pftEvenOdd);
bool ReverseSolution() {return m_ReverseOutput;};
void ReverseSolution(bool value) {m_ReverseOutput = value;};
bool StrictlySimple() {return m_StrictSimple;};
void StrictlySimple(bool value) {m_StrictSimple = value;};
//set the callback function for z value filling on intersections (otherwise Z is 0)
#ifdef use_xyz
void ZFillFunction(ZFillCallback zFillFunc);
#endif
protected:
void Reset();
virtual bool ExecuteInternal();
private:
PolyOutList m_PolyOuts;
JoinList m_Joins;
JoinList m_GhostJoins;
IntersectList m_IntersectList;
ClipType m_ClipType;
typedef std::priority_queue<cInt> ScanbeamList;
ScanbeamList m_Scanbeam;
TEdge *m_ActiveEdges;
TEdge *m_SortedEdges;
bool m_ExecuteLocked;
PolyFillType m_ClipFillType;
PolyFillType m_SubjFillType;
bool m_ReverseOutput;
bool m_UsingPolyTree;
bool m_StrictSimple;
#ifdef use_xyz
ZFillCallback m_ZFill; //custom callback
#endif
void SetWindingCount(TEdge& edge);
bool IsEvenOddFillType(const TEdge& edge) const;
bool IsEvenOddAltFillType(const TEdge& edge) const;
void InsertScanbeam(const cInt Y);
cInt PopScanbeam();
void InsertLocalMinimaIntoAEL(const cInt botY);
void InsertEdgeIntoAEL(TEdge *edge, TEdge* startEdge);
void AddEdgeToSEL(TEdge *edge);
void CopyAELToSEL();
void DeleteFromSEL(TEdge *e);
void DeleteFromAEL(TEdge *e);
void UpdateEdgeIntoAEL(TEdge *&e);
void SwapPositionsInSEL(TEdge *edge1, TEdge *edge2);
bool IsContributing(const TEdge& edge) const;
bool IsTopHorz(const cInt XPos);
void SwapPositionsInAEL(TEdge *edge1, TEdge *edge2);
void DoMaxima(TEdge *e);
void ProcessHorizontals(bool IsTopOfScanbeam);
void ProcessHorizontal(TEdge *horzEdge, bool isTopOfScanbeam);
void AddLocalMaxPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
OutPt* AddLocalMinPoly(TEdge *e1, TEdge *e2, const IntPoint &pt);
OutRec* GetOutRec(int idx);
void AppendPolygon(TEdge *e1, TEdge *e2);
void IntersectEdges(TEdge *e1, TEdge *e2, IntPoint &pt);
OutRec* CreateOutRec();
OutPt* AddOutPt(TEdge *e, const IntPoint &pt);
void DisposeAllOutRecs();
void DisposeOutRec(PolyOutList::size_type index);
bool ProcessIntersections(const cInt topY);
void BuildIntersectList(const cInt topY);
void ProcessIntersectList();
void ProcessEdgesAtTopOfScanbeam(const cInt topY);
void BuildResult(Paths& polys);
void BuildResult2(PolyTree& polytree);
void SetHoleState(TEdge *e, OutRec *outrec);
void DisposeIntersectNodes();
bool FixupIntersectionOrder();
void FixupOutPolygon(OutRec &outrec);
bool IsHole(TEdge *e);
bool FindOwnerFromSplitRecs(OutRec &outRec, OutRec *&currOrfl);
void FixHoleLinkage(OutRec &outrec);
void AddJoin(OutPt *op1, OutPt *op2, const IntPoint offPt);
void ClearJoins();
void ClearGhostJoins();
void AddGhostJoin(OutPt *op, const IntPoint offPt);
bool JoinPoints(Join *j, OutRec* outRec1, OutRec* outRec2);
void JoinCommonEdges();
void DoSimplePolygons();
void FixupFirstLefts1(OutRec* OldOutRec, OutRec* NewOutRec);
void FixupFirstLefts2(OutRec* OldOutRec, OutRec* NewOutRec);
#ifdef use_xyz
void SetZ(IntPoint& pt, TEdge& e1, TEdge& e2);
#endif
};
//------------------------------------------------------------------------------
class ClipperOffset
{
public:
ClipperOffset(double miterLimit = 2.0, double roundPrecision = 0.25);
~ClipperOffset();
void AddPath(const Path& path, JoinType joinType, EndType endType);
void AddPaths(const Paths& paths, JoinType joinType, EndType endType);
void Execute(Paths& solution, double delta);
void Execute(PolyTree& solution, double delta);
void Clear();
double MiterLimit;
double ArcTolerance;
private:
Paths m_destPolys;
Path m_srcPoly;
Path m_destPoly;
std::vector<DoublePoint> m_normals;
double m_delta, m_sinA, m_sin, m_cos;
double m_miterLim, m_StepsPerRad;
IntPoint m_lowest;
PolyNode m_polyNodes;
void FixOrientations();
void DoOffset(double delta);
void OffsetPoint(int j, int& k, JoinType jointype);
void DoSquare(int j, int k);
void DoMiter(int j, int k, double r);
void DoRound(int j, int k);
};
//------------------------------------------------------------------------------
class clipperException : public std::exception
{
public:
clipperException(const char* description): m_descr(description) {}
virtual ~clipperException() throw() {}
virtual const char* what() const throw() {return m_descr.c_str();}
private:
std::string m_descr;
};
//------------------------------------------------------------------------------
} //ClipperLib namespace
#endif //clipper_hpp

321
decode.cc
View File

@ -6,20 +6,19 @@
#include <zlib.h>
#include <math.h>
#include "vector_tile.pb.h"
#include "tile.h"
extern "C" {
#include "projection.h"
#include "projection.h"
}
// https://github.com/mapbox/mapnik-vector-tile/blob/master/src/vector_tile_compression.hpp
inline bool is_compressed(std::string const& data) {
return data.size() > 2 &&
(((uint8_t)data[0] == 0x78 && (uint8_t)data[1] == 0x9C) ||
((uint8_t)data[0] == 0x1F && (uint8_t)data[1] == 0x8B));
inline bool is_compressed(std::string const &data) {
return data.size() > 2 && (((uint8_t) data[0] == 0x78 && (uint8_t) data[1] == 0x9C) || ((uint8_t) data[0] == 0x1F && (uint8_t) data[1] == 0x8B));
}
// https://github.com/mapbox/mapnik-vector-tile/blob/master/src/vector_tile_compression.hpp
inline int decompress(std::string const& input, std::string & output) {
inline int decompress(std::string const &input, std::string &output) {
z_stream inflate_s;
inflate_s.zalloc = Z_NULL;
inflate_s.zfree = Z_NULL;
@ -29,13 +28,13 @@ inline int decompress(std::string const& input, std::string & output) {
if (inflateInit2(&inflate_s, 32 + 15) != Z_OK) {
fprintf(stderr, "error: %s\n", inflate_s.msg);
}
inflate_s.next_in = (Bytef *)input.data();
inflate_s.next_in = (Bytef *) input.data();
inflate_s.avail_in = input.size();
size_t length = 0;
do {
output.resize(length + 2 * input.size());
inflate_s.avail_out = 2 * input.size();
inflate_s.next_out = (Bytef *)(output.data() + length);
inflate_s.next_out = (Bytef *) (output.data() + length);
int ret = inflate(&inflate_s, Z_FINISH);
if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR) {
fprintf(stderr, "error: %s\n", inflate_s.msg);
@ -53,15 +52,42 @@ int dezig(unsigned n) {
return (n >> 1) ^ (-(n & 1));
}
void handle(std::string message, int z, unsigned x, unsigned y) {
void printq(const char *s) {
putchar('"');
for (; *s; s++) {
if (*s == '\\' || *s == '"') {
printf("\\%c", *s);
} else if (*s >= 0 && *s < ' ') {
printf("\\u%04x", *s);
} else {
putchar(*s);
}
}
putchar('"');
}
struct draw {
int op;
double lon;
double lat;
draw(int op, double lon, double lat) {
this->op = op;
this->lon = lon;
this->lat = lat;
}
};
void handle(std::string message, int z, unsigned x, unsigned y, int describe) {
GOOGLE_PROTOBUF_VERIFY_VERSION;
int within = 0;
// https://github.com/mapbox/mapnik-vector-tile/blob/master/examples/c%2B%2B/tileinfo.cpp
mapnik::vector::tile tile;
if (is_compressed(message)) {
std::string uncompressed;
decompress(message,uncompressed);
decompress(message, uncompressed);
if (!tile.ParseFromString(uncompressed)) {
fprintf(stderr, "Couldn't decompress tile %d/%u/%u\n", z, x, y);
exit(EXIT_FAILURE);
@ -71,6 +97,14 @@ void handle(std::string message, int z, unsigned x, unsigned y) {
exit(EXIT_FAILURE);
}
printf("{ \"type\": \"FeatureCollection\"");
if (describe) {
printf(", \"properties\": { \"zoom\": %d, \"x\": %d, \"y\": %d }", z, x, y);
}
printf(", \"features\": [\n");
for (int l = 0; l < tile.layers_size(); l++) {
mapnik::vector::tile_layer layer = tile.layers(l);
int extent = layer.extent();
@ -79,16 +113,57 @@ void handle(std::string message, int z, unsigned x, unsigned y) {
mapnik::vector::tile_feature feat = layer.features(f);
int px = 0, py = 0;
if (within) {
printf(",\n");
}
within = 1;
printf("{ \"type\": \"Feature\"");
printf(", \"properties\": { ");
for (int t = 0; t + 1 < feat.tags_size(); t += 2) {
if (t != 0) {
printf(", ");
}
const char *key = layer.keys(feat.tags(t)).c_str();
mapnik::vector::tile_value const &val = layer.values(feat.tags(t + 1));
if (val.has_string_value()) {
printq(key);
printf(": ");
printq(val.string_value().c_str());
} else if (val.has_int_value()) {
printq(key);
printf(": %lld", (long long) val.int_value());
} else if (val.has_double_value()) {
printq(key);
printf(": %g", val.double_value());
} else if (val.has_float_value()) {
printq(key);
printf(": %g", val.float_value());
} else if (val.has_sint_value()) {
printq(key);
printf(": %lld", (long long) val.sint_value());
} else if (val.has_uint_value()) {
printq(key);
printf(": %lld", (long long) val.uint_value());
} else if (val.has_bool_value()) {
printq(key);
printf(": %s", val.bool_value() ? "true" : "false");
}
}
printf(" }, \"geometry\": { ");
std::vector<draw> ops;
for (int g = 0; g < feat.geometry_size(); g++) {
uint32_t geom = feat.geometry(g);
uint32_t op = geom & 7;
uint32_t count = geom >> 3;
if (op == 1 || op == 2) {
if (op == 1) {
printf("\n");
}
if (op == VT_MOVETO || op == VT_LINETO) {
for (unsigned k = 0; k < count; k++) {
px += dezig(feat.geometry(g + 1));
py += dezig(feat.geometry(g + 2));
@ -100,12 +175,144 @@ void handle(std::string message, int z, unsigned x, unsigned y) {
double lat, lon;
tile2latlon(wx, wy, 32, &lat, &lon);
printf("%f,%f ", lat, lon);
ops.push_back(draw(op, lon, lat));
}
} else {
ops.push_back(draw(op, 0, 0));
}
}
if (feat.type() == VT_POINT) {
if (ops.size() == 1) {
printf("\"type\": \"Point\", \"coordinates\": [ %f, %f ]", ops[0].lon, ops[0].lat);
} else {
printf("\"type\": \"MultiPoint\", \"coordinates\": [ ");
for (unsigned i = 0; i < ops.size(); i++) {
if (i != 0) {
printf(", ");
}
printf("[ %f, %f ]", ops[i].lon, ops[i].lat);
}
printf(" ]");
}
} else if (feat.type() == VT_LINE) {
int movetos = 0;
for (unsigned i = 0; i < ops.size(); i++) {
if (ops[i].op == VT_MOVETO) {
movetos++;
}
}
if (movetos < 2) {
printf("\"type\": \"LineString\", \"coordinates\": [ ");
for (unsigned i = 0; i < ops.size(); i++) {
if (i != 0) {
printf(", ");
}
printf("[ %f, %f ]", ops[i].lon, ops[i].lat);
}
printf(" ]");
} else {
printf("\"type\": \"MultiLineString\", \"coordinates\": [ [ ");
int state = 0;
for (unsigned i = 0; i < ops.size(); i++) {
if (ops[i].op == VT_MOVETO) {
if (state == 0) {
printf("[ %f, %f ]", ops[i].lon, ops[i].lat);
state = 1;
} else {
printf(" ], [ ");
printf("[ %f, %f ]", ops[i].lon, ops[i].lat);
state = 1;
}
} else {
printf(", [ %f, %f ]", ops[i].lon, ops[i].lat);
}
}
printf(" ] ]");
}
} else if (feat.type() == VT_POLYGON) {
std::vector<std::vector<draw> > rings;
std::vector<double> areas;
for (unsigned i = 0; i < ops.size(); i++) {
if (ops[i].op == VT_MOVETO) {
rings.push_back(std::vector<draw>());
areas.push_back(0);
}
int n = rings.size() - 1;
if (n >= 0) {
rings[n].push_back(ops[i]);
}
}
int outer = 0;
for (unsigned i = 0; i < rings.size(); i++) {
double area = 0;
for (unsigned k = 0; k < rings[i].size(); k++) {
if (rings[i][k].op != VT_CLOSEPATH) {
area += rings[i][k].lon * rings[i][(k + 1) % rings[i].size()].lat;
area -= rings[i][k].lat * rings[i][(k + 1) % rings[i].size()].lon;
}
}
areas[i] = area;
if (areas[i] <= 0) {
outer++;
}
// printf("area %f\n", area / .00000274 / .00000274);
}
if (outer > 1) {
printf("\"type\": \"MultiPolygon\", \"coordinates\": [ [ [ ");
} else {
printf("\"type\": \"Polygon\", \"coordinates\": [ [ ");
}
int state = 0;
for (unsigned i = 0; i < rings.size(); i++) {
if (areas[i] <= 0) {
if (state != 0) {
// new multipolygon
printf(" ] ], [ [ ");
}
state = 1;
}
if (state == 2) {
// new ring in the same polygon
printf(" ], [ ");
}
for (unsigned j = 0; j < rings[i].size(); j++) {
if (rings[i][j].op != VT_CLOSEPATH) {
if (j != 0) {
printf(", ");
}
printf("[ %f, %f ]", rings[i][j].lon, rings[i][j].lat);
}
}
state = 2;
}
if (outer > 1) {
printf(" ] ] ]");
} else {
printf(" ] ]");
}
}
printf(" } }\n");
}
}
printf("] }\n");
}
void decode(char *fname, int z, unsigned x, unsigned y) {
@ -114,46 +321,78 @@ void decode(char *fname, int z, unsigned x, unsigned y) {
unsigned ox = x, oy = y;
if (sqlite3_open(fname, &db) != SQLITE_OK) {
fprintf(stderr, "%s: %s\n", fname, sqlite3_errmsg(db));
fprintf(stderr, "%s: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
int handled = 0;
while (z >= 0 && !handled) {
const char *sql = "SELECT tile_data from tiles where zoom_level = ? and tile_column = ? and tile_row = ?;";
if (z < 0) {
const char *sql = "SELECT tile_data, zoom_level, tile_column, tile_row from tiles order by zoom_level, tile_column, tile_row;";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
fprintf(stderr, "%s: select failed: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
sqlite3_bind_int(stmt, 1, z);
sqlite3_bind_int(stmt, 2, x);
sqlite3_bind_int(stmt, 3, (1LL << z) - 1 - y);
printf("{ \"type\": \"FeatureCollection\", \"features\": [\n");
int within = 0;
while (sqlite3_step(stmt) == SQLITE_ROW) {
if (within) {
printf(",\n");
}
within = 1;
int len = sqlite3_column_bytes(stmt, 0);
int z = sqlite3_column_int(stmt, 1);
int x = sqlite3_column_int(stmt, 2);
int y = sqlite3_column_int(stmt, 3);
y = (1LL << z) - 1 - y;
const char *s = (const char *) sqlite3_column_blob(stmt, 0);
if (z != oz) {
fprintf(stderr, "%s: Warning: using tile %d/%u/%u instead of %d/%u/%u\n", fname, z, x, y, oz, ox, oy);
}
handle(std::string(s, len), z, x, y);
handled = 1;
handle(std::string(s, len), z, x, y, 1);
}
sqlite3_finalize(stmt);
printf("] }\n");
z--;
x /= 2;
y /= 2;
sqlite3_finalize(stmt);
} else {
int handled = 0;
while (z >= 0 && !handled) {
const char *sql = "SELECT tile_data from tiles where zoom_level = ? and tile_column = ? and tile_row = ?;";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
fprintf(stderr, "%s: select failed: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
sqlite3_bind_int(stmt, 1, z);
sqlite3_bind_int(stmt, 2, x);
sqlite3_bind_int(stmt, 3, (1LL << z) - 1 - y);
while (sqlite3_step(stmt) == SQLITE_ROW) {
int len = sqlite3_column_bytes(stmt, 0);
const char *s = (const char *) sqlite3_column_blob(stmt, 0);
if (z != oz) {
fprintf(stderr, "%s: Warning: using tile %d/%u/%u instead of %d/%u/%u\n", fname, z, x, y, oz, ox, oy);
}
handle(std::string(s, len), z, x, y, 0);
handled = 1;
}
sqlite3_finalize(stmt);
z--;
x /= 2;
y /= 2;
}
}
if (sqlite3_close(db) != SQLITE_OK) {
fprintf(stderr, "%s: could not close database: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
if (sqlite3_close(db) != SQLITE_OK) {
fprintf(stderr, "%s: could not close database: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
}
void usage(char **argv) {
@ -170,11 +409,13 @@ int main(int argc, char **argv) {
usage(argv);
}
if (argc != optind + 4) {
if (argc == optind + 4) {
decode(argv[optind], atoi(argv[optind + 1]), atoi(argv[optind + 2]), atoi(argv[optind + 3]));
} else if (argc == optind + 1) {
decode(argv[optind], -1, -1, -1);
} else {
usage(argv);
}
decode(argv[optind], atoi(argv[optind + 1]), atoi(argv[optind + 2]), atoi(argv[optind + 3]));
return 0;
}

View File

@ -7,7 +7,7 @@ void enumerate(char *fname) {
sqlite3 *db;
if (sqlite3_open(fname, &db) != SQLITE_OK) {
fprintf(stderr, "%s: %s\n", fname, sqlite3_errmsg(db));
fprintf(stderr, "%s: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
@ -24,16 +24,16 @@ void enumerate(char *fname) {
long long x = sqlite3_column_int(stmt, 1);
long long y = sqlite3_column_int(stmt, 2);
y = (1LL << zoom) - y;
y = (1LL << zoom) - 1 - y;
printf("%s %lld %lld %lld\n", fname, zoom, x, y);
}
sqlite3_finalize(stmt);
if (sqlite3_close(db) != SQLITE_OK) {
fprintf(stderr, "%s: could not close database: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
if (sqlite3_close(db) != SQLITE_OK) {
fprintf(stderr, "%s: could not close database: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
}
void usage(char **argv) {
@ -43,7 +43,7 @@ void usage(char **argv) {
int main(int argc, char **argv) {
extern int optind;
//extern char *optarg;
// extern char *optarg;
int i;
while ((i = getopt(argc, argv, "")) != -1) {

1674
geojson.c

File diff suppressed because it is too large Load Diff

View File

@ -8,17 +8,26 @@
#include <unistd.h>
#include <math.h>
#include <sqlite3.h>
#include <limits.h>
#include "geometry.hh"
#include "clipper/clipper.hpp"
extern "C" {
#include "tile.h"
#include "clip.h"
#include "projection.h"
#include "tile.h"
#include "clip.h"
#include "projection.h"
}
drawvec decode_geometry(char **meta, int z, unsigned tx, unsigned ty, int detail) {
drawvec decode_geometry(char **meta, int z, unsigned tx, unsigned ty, int detail, long long *bbox) {
drawvec out;
bbox[0] = LONG_LONG_MAX;
bbox[1] = LONG_LONG_MAX;
bbox[2] = LONG_LONG_MIN;
bbox[3] = LONG_LONG_MIN;
long long wx = initial_x, wy = initial_y;
while (1) {
draw d;
@ -28,18 +37,35 @@ drawvec decode_geometry(char **meta, int z, unsigned tx, unsigned ty, int detail
}
if (d.op == VT_MOVETO || d.op == VT_LINETO) {
int wx, wy;
deserialize_int(meta, &wx);
deserialize_int(meta, &wy);
long long dx, dy;
long long wwx = (unsigned) wx;
long long wwy = (unsigned) wy;
deserialize_long_long(meta, &dx);
deserialize_long_long(meta, &dy);
wx += dx << geometry_scale;
wy += dy << geometry_scale;
long long wwx = wx;
long long wwy = wy;
if (z != 0) {
wwx -= tx << (32 - z);
wwy -= ty << (32 - z);
}
if (wwx < bbox[0]) {
bbox[0] = wwx;
}
if (wwy < bbox[1]) {
bbox[1] = wwy;
}
if (wwx > bbox[2]) {
bbox[2] = wwx;
}
if (wwy > bbox[3]) {
bbox[3] = wwy;
}
d.x = wwx;
d.y = wwy;
}
@ -59,7 +85,7 @@ void to_tile_scale(drawvec &geom, int z, int detail) {
}
}
drawvec remove_noop(drawvec geom, int type) {
drawvec remove_noop(drawvec geom, int type, int shift) {
// first pass: remove empty linetos
long long x = 0, y = 0;
@ -67,16 +93,17 @@ drawvec remove_noop(drawvec geom, int type) {
unsigned i;
for (i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_LINETO && geom[i].x == x && geom[i].y == y) {
if (geom[i].op == VT_LINETO && (geom[i].x >> shift) == x && (geom[i].y >> shift) == y) {
continue;
}
if (geom[i].op == VT_CLOSEPATH) {
fprintf(stderr, "Shouldn't happen\n");
out.push_back(geom[i]);
} else { /* moveto or lineto */
out.push_back(geom[i]);
x = geom[i].x;
y = geom[i].y;
x = geom[i].x >> shift;
y = geom[i].y >> shift;
}
}
@ -96,7 +123,8 @@ drawvec remove_noop(drawvec geom, int type) {
}
if (geom[i + 1].op == VT_CLOSEPATH) {
i++; // also remove unused closepath
fprintf(stderr, "Shouldn't happen\n");
i++; // also remove unused closepath
continue;
}
}
@ -112,7 +140,7 @@ drawvec remove_noop(drawvec geom, int type) {
for (i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
if (i > 0 && geom[i - 1].op == VT_LINETO && geom[i - 1].x == geom[i].x && geom[i - 1].y == geom[i].y) {
if (i > 0 && geom[i - 1].op == VT_LINETO && (geom[i - 1].x >> shift) == (geom[i].x >> shift) && (geom[i - 1].y >> shift) == (geom[i].y >> shift)) {
continue;
}
}
@ -179,151 +207,112 @@ drawvec shrink_lines(drawvec &geom, int z, int detail, int basezoom, long long *
}
#endif
static bool inside(draw d, int edge, long long area, long long buffer) {
long long clip_buffer = buffer * area / 256;
static void decode_clipped(ClipperLib::PolyNode *t, drawvec &out) {
// To make the GeoJSON come out right, we need to do each of the
// outer rings followed by its children if any, and then go back
// to do any outer-ring children of those children as a new top level.
switch (edge) {
case 0: // top
return d.y > -clip_buffer;
case 1: // right
return d.x < area + clip_buffer;
case 2: // bottom
return d.y < area + clip_buffer;
case 3: // left
return d.x > -clip_buffer;
ClipperLib::Path p = t->Contour;
for (unsigned i = 0; i < p.size(); i++) {
out.push_back(draw((i == 0) ? VT_MOVETO : VT_LINETO, p[i].X, p[i].Y));
}
if (p.size() > 0) {
out.push_back(draw(VT_LINETO, p[0].X, p[0].Y));
}
fprintf(stderr, "internal error inside\n");
exit(EXIT_FAILURE);
}
// http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
static draw get_line_intersection(draw p0, draw p1, draw p2, draw p3) {
double s1_x = p1.x - p0.x;
double s1_y = p1.y - p0.y;
double s2_x = p3.x - p2.x;
double s2_y = p3.y - p2.y;
double t;
//s = (-s1_y * (p0.x - p2.x) + s1_x * (p0.y - p2.y)) / (-s2_x * s1_y + s1_x * s2_y);
t = ( s2_x * (p0.y - p2.y) - s2_y * (p0.x - p2.x)) / (-s2_x * s1_y + s1_x * s2_y);
return draw(VT_LINETO, p0.x + (t * s1_x), p0.y + (t * s1_y));
}
static draw intersect(draw a, draw b, int edge, long long area, long long buffer) {
long long clip_buffer = buffer * area / 256;
switch (edge) {
case 0: // top
return get_line_intersection(a, b, draw(VT_MOVETO, -clip_buffer, -clip_buffer), draw(VT_MOVETO, area + clip_buffer, -clip_buffer));
break;
case 1: // right
return get_line_intersection(a, b, draw(VT_MOVETO, area + clip_buffer, -clip_buffer), draw(VT_MOVETO, area + clip_buffer, area + clip_buffer));
break;
case 2: // bottom
return get_line_intersection(a, b, draw(VT_MOVETO, area + clip_buffer, area + clip_buffer), draw(VT_MOVETO, -clip_buffer, area + clip_buffer));
break;
case 3: // left
return get_line_intersection(a, b, draw(VT_MOVETO, -clip_buffer, area + clip_buffer), draw(VT_MOVETO, -clip_buffer, -clip_buffer));
break;
}
fprintf(stderr, "internal error intersecting\n");
exit(EXIT_FAILURE);
}
// http://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm
static drawvec clip_poly1(drawvec &geom, int z, int detail, int buffer) {
drawvec out = geom;
long long area = 0xFFFFFFFF;
if (z != 0) {
area = 1LL << (32 - z);
}
for (int edge = 0; edge < 4; edge++) {
if (out.size() > 0) {
drawvec in = out;
out.resize(0);
draw S = in[in.size() - 1];
for (unsigned e = 0; e < in.size(); e++) {
draw E = in[e];
if (inside(E, edge, area, buffer)) {
if (!inside(S, edge, area, buffer)) {
out.push_back(intersect(S, E, edge, area, buffer));
}
out.push_back(E);
} else if (inside(S, edge, area, buffer)) {
out.push_back(intersect(S, E, edge, area, buffer));
}
S = E;
}
for (int n = 0; n < t->ChildCount(); n++) {
ClipperLib::Path p = t->Childs[n]->Contour;
for (unsigned i = 0; i < p.size(); i++) {
out.push_back(draw((i == 0) ? VT_MOVETO : VT_LINETO, p[i].X, p[i].Y));
}
if (p.size() > 0) {
out.push_back(draw(VT_LINETO, p[0].X, p[0].Y));
}
}
if (out.size() > 0) {
out[0].op = VT_MOVETO;
for (unsigned i = 1; i < out.size(); i++) {
out[i].op = VT_LINETO;
for (int n = 0; n < t->ChildCount(); n++) {
for (int m = 0; m < t->Childs[n]->ChildCount(); m++) {
decode_clipped(t->Childs[n]->Childs[m], out);
}
}
return out;
}
drawvec clip_poly(drawvec &geom, int z, int detail, int buffer) {
if (z == 0) {
return geom;
}
drawvec out;
drawvec clean_or_clip_poly(drawvec &geom, int z, int detail, int buffer, bool clip) {
ClipperLib::Clipper clipper(ClipperLib::ioStrictlySimple);
for (unsigned i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
unsigned j;
for (j = i + 1; j < geom.size(); j++) {
if (geom[j].op == VT_CLOSEPATH || geom[j].op == VT_MOVETO) {
if (geom[j].op != VT_LINETO) {
break;
}
}
ClipperLib::Path path;
drawvec tmp;
for (unsigned k = i; k < j; k++) {
tmp.push_back(geom[k]);
}
tmp = clip_poly1(tmp, z, detail, buffer);
for (unsigned k = 0; k < tmp.size(); k++) {
out.push_back(tmp[k]);
path.push_back(ClipperLib::IntPoint(geom[k].x, geom[k].y));
}
if (j >= geom.size() || geom[j].op == VT_CLOSEPATH) {
out.push_back(draw(VT_CLOSEPATH, 0, 0));
i = j;
} else {
i = j - 1;
if (!clipper.AddPath(path, ClipperLib::ptSubject, true)) {
#if 0
fprintf(stderr, "Couldn't add polygon for clipping:");
for (unsigned k = i; k < j; k++) {
fprintf(stderr, " %lld,%lld", geom[k].x, geom[k].y);
}
fprintf(stderr, "\n");
#endif
}
i = j - 1;
} else {
out.push_back(geom[i]);
fprintf(stderr, "Unexpected operation in polygon %d\n", (int) geom[i].op);
exit(EXIT_FAILURE);
}
}
if (clip) {
long long area = 0xFFFFFFFF;
if (z != 0) {
area = 1LL << (32 - z);
}
long long clip_buffer = buffer * area / 256;
ClipperLib::Path edge;
edge.push_back(ClipperLib::IntPoint(-clip_buffer, -clip_buffer));
edge.push_back(ClipperLib::IntPoint(area + clip_buffer, -clip_buffer));
edge.push_back(ClipperLib::IntPoint(area + clip_buffer, area + clip_buffer));
edge.push_back(ClipperLib::IntPoint(-clip_buffer, area + clip_buffer));
edge.push_back(ClipperLib::IntPoint(-clip_buffer, -clip_buffer));
clipper.AddPath(edge, ClipperLib::ptClip, true);
}
ClipperLib::PolyTree clipped;
if (clip) {
if (!clipper.Execute(ClipperLib::ctIntersection, clipped)) {
fprintf(stderr, "Polygon clip failed\n");
}
} else {
if (!clipper.Execute(ClipperLib::ctUnion, clipped)) {
fprintf(stderr, "Polygon clean failed\n");
}
}
drawvec out;
for (int i = 0; i < clipped.ChildCount(); i++) {
decode_clipped(clipped.Childs[i], out);
}
return out;
}
drawvec reduce_tiny_poly(drawvec &geom, int z, int detail, bool *reduced, double *accum_area) {
drawvec out;
long long pixel = (1 << (32 - detail - z)) * 3;
long long pixel = (1 << (32 - detail - z)) * 2;
*reduced = true;
@ -331,39 +320,35 @@ drawvec reduce_tiny_poly(drawvec &geom, int z, int detail, bool *reduced, double
if (geom[i].op == VT_MOVETO) {
unsigned j;
for (j = i + 1; j < geom.size(); j++) {
if (geom[j].op == VT_CLOSEPATH) {
if (geom[j].op != VT_LINETO) {
break;
}
}
if (j + 1 < geom.size() && geom[j + 1].op == VT_CLOSEPATH) {
fprintf(stderr, "double closepath\n");
}
double area = 0;
for (unsigned k = i; k < j; k++) {
area += geom[k].x * geom[i + ((k - i + 1) % (j - i))].y;
area -= geom[k].y * geom[i + ((k - i + 1) % (j - i))].x;
}
area = fabs(area / 2);
area = area / 2;
if (area <= pixel * pixel) {
//printf("area is only %f vs %lld so using square\n", area, pixel * pixel);
if (fabs(area) <= pixel * pixel) {
// printf("area is only %f vs %lld so using square\n", area, pixel * pixel);
*accum_area += area;
if (*accum_area > pixel * pixel) {
// XXX use centroid;
out.push_back(draw(VT_MOVETO, geom[i].x, geom[i].y));
out.push_back(draw(VT_LINETO, geom[i].x + pixel, geom[i].y));
out.push_back(draw(VT_LINETO, geom[i].x + pixel, geom[i].y + pixel));
out.push_back(draw(VT_LINETO, geom[i].x, geom[i].y + pixel));
out.push_back(draw(VT_CLOSEPATH, geom[i].x, geom[i].y));
out.push_back(draw(VT_MOVETO, geom[i].x - pixel / 2, geom[i].y - pixel / 2));
out.push_back(draw(VT_LINETO, geom[i].x + pixel / 2, geom[i].y - pixel / 2));
out.push_back(draw(VT_LINETO, geom[i].x + pixel / 2, geom[i].y + pixel / 2));
out.push_back(draw(VT_LINETO, geom[i].x - pixel / 2, geom[i].y + pixel / 2));
out.push_back(draw(VT_LINETO, geom[i].x - pixel / 2, geom[i].y - pixel / 2));
*accum_area -= pixel * pixel;
}
} else {
//printf("area is %f so keeping instead of %lld\n", area, pixel * pixel);
// printf("area is %f so keeping instead of %lld\n", area, pixel * pixel);
for (unsigned k = i; k <= j && k < geom.size(); k++) {
out.push_back(geom[k]);
@ -372,9 +357,15 @@ drawvec reduce_tiny_poly(drawvec &geom, int z, int detail, bool *reduced, double
*reduced = false;
}
i = j;
i = j - 1;
} else {
fprintf(stderr, "how did we get here with %d?\n", geom[i].op);
fprintf(stderr, "how did we get here with %d in %d?\n", geom[i].op, (int) geom.size());
for (unsigned n = 0; n < geom.size(); n++) {
fprintf(stderr, "%d/%lld/%lld ", geom[n].op, geom[n].x, geom[n].y);
}
fprintf(stderr, "\n");
out.push_back(geom[i]);
}
}
@ -382,11 +373,68 @@ drawvec reduce_tiny_poly(drawvec &geom, int z, int detail, bool *reduced, double
return out;
}
drawvec clip_point(drawvec &geom, int z, int detail, long long buffer) {
drawvec out;
unsigned i;
long long min = 0;
long long area = 0xFFFFFFFF;
if (z != 0) {
area = 1LL << (32 - z);
min -= buffer * area / 256;
area += buffer * area / 256;
}
for (i = 0; i < geom.size(); i++) {
if (geom[i].x >= min && geom[i].y >= min && geom[i].x <= area && geom[i].y <= area) {
out.push_back(geom[i]);
}
}
return out;
}
int quick_check(long long *bbox, int z, int detail, long long buffer) {
long long min = 0;
long long area = 0xFFFFFFFF;
if (z != 0) {
area = 1LL << (32 - z);
min -= buffer * area / 256;
area += buffer * area / 256;
}
// bbox entirely outside the tile
if (bbox[0] > area || bbox[1] > area) {
return 0;
}
if (bbox[2] < min || bbox[3] < min) {
return 0;
}
// bbox entirely within the tile
if (bbox[0] > min && bbox[1] > min && bbox[2] < area && bbox[3] < area) {
return 1;
}
// some overlap of edge
return 2;
}
drawvec clip_lines(drawvec &geom, int z, int detail, long long buffer) {
drawvec out;
unsigned i;
long long min = 0;
long long area = 0xFFFFFFFF;
if (z != 0) {
area = 1LL << (32 - z);
min -= buffer * area / 256;
area += buffer * area / 256;
}
for (i = 0; i < geom.size(); i++) {
if (i > 0 && (geom[i - 1].op == VT_MOVETO || geom[i - 1].op == VT_LINETO) && geom[i].op == VT_LINETO) {
double x1 = geom[i - 1].x;
@ -395,24 +443,15 @@ drawvec clip_lines(drawvec &geom, int z, int detail, long long buffer) {
double x2 = geom[i - 0].x;
double y2 = geom[i - 0].y;
long long min = 0;
long long area = 0xFFFFFFFF;
if (z != 0) {
area = 1LL << (32 - z);
min -= buffer * area / 256;
area += buffer * area / 256;
}
int c = clip(&x1, &y1, &x2, &y2, min, min, area, area);
if (c > 1) { // clipped
if (c > 1) { // clipped
out.push_back(draw(VT_MOVETO, x1, y1));
out.push_back(draw(VT_LINETO, x2, y2));
out.push_back(draw(VT_MOVETO, geom[i].x, geom[i].y));
} else if (c == 1) { // unchanged
} else if (c == 1) { // unchanged
out.push_back(geom[i]);
} else { // clipped away entirely
} else { // clipped away entirely
out.push_back(draw(VT_MOVETO, geom[i].x, geom[i].y));
}
} else {
@ -476,9 +515,7 @@ static void douglas_peucker(drawvec &geom, int start, int n, double e) {
// find index idx of element with max_distance
int i;
for (i = first + 1; i < second; i++) {
double temp_dist = square_distance_from_line(geom[start + i].x, geom[start + i].y,
geom[start + first].x, geom[start + first].y,
geom[start + second].x, geom[start + second].y);
double temp_dist = square_distance_from_line(geom[start + i].x, geom[start + i].y, geom[start + first].x, geom[start + first].y, geom[start + second].x, geom[start + second].y);
double distance = fabs(temp_dist);
@ -522,7 +559,7 @@ drawvec simplify_lines(drawvec &geom, int z, int detail) {
if (geom[i].op == VT_MOVETO) {
unsigned j;
for (j = i + 1; j < geom.size(); j++) {
if (geom[j].op == VT_CLOSEPATH || geom[j].op == VT_MOVETO) {
if (geom[j].op != VT_LINETO) {
break;
}
}
@ -548,6 +585,10 @@ drawvec simplify_lines(drawvec &geom, int z, int detail) {
drawvec reorder_lines(drawvec &geom) {
// Only reorder simple linestrings with a single moveto
if (geom.size() == 0) {
return geom;
}
unsigned i;
for (i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_MOVETO) {
@ -582,3 +623,74 @@ drawvec reorder_lines(drawvec &geom) {
return geom;
}
drawvec fix_polygon(drawvec &geom) {
int outer = 1;
drawvec out;
unsigned i;
for (i = 0; i < geom.size(); i++) {
if (geom[i].op == VT_CLOSEPATH) {
outer = 1;
} else if (geom[i].op == VT_MOVETO) {
// Find the end of the ring
unsigned j;
for (j = i + 1; j < geom.size(); j++) {
if (geom[j].op != VT_LINETO) {
break;
}
}
// Make a temporary copy of the ring.
// Close it if it isn't closed.
drawvec ring;
for (unsigned a = i; a < j; a++) {
ring.push_back(geom[a]);
}
if (j - i != 0 && (ring[0].x != ring[j - i - 1].x || ring[0].y != ring[j - i - 1].y)) {
ring.push_back(ring[0]);
}
// Reverse ring if winding order doesn't match
// inner/outer expectation
double area = 0;
for (unsigned k = 0; k < ring.size(); k++) {
area += (long double) ring[k].x * (long double) ring[(k + 1) % ring.size()].y;
area -= (long double) ring[k].y * (long double) ring[(k + 1) % ring.size()].x;
}
if ((area > 0) != outer) {
drawvec tmp;
for (int a = ring.size() - 1; a >= 0; a--) {
tmp.push_back(ring[a]);
}
ring = tmp;
}
// Copy ring into output, fixing the moveto/lineto ops if necessary because of
// reversal or closing
for (unsigned a = 0; a < ring.size(); a++) {
if (a == 0) {
out.push_back(draw(VT_MOVETO, ring[a].x, ring[a].y));
} else {
out.push_back(draw(VT_LINETO, ring[a].x, ring[a].y));
}
}
// Next ring or polygon begins on the non-lineto that ended this one
// and is not an outer ring unless there is a terminator first
i = j - 1;
outer = 0;
} else {
fprintf(stderr, "Internal error: polygon ring begins with %d, not moveto\n", geom[i].op);
exit(EXIT_FAILURE);
}
}
return out;
}

View File

@ -10,16 +10,20 @@ struct draw {
this->y = y;
}
draw() { }
draw() {
}
};
typedef std::vector<draw> drawvec;
drawvec decode_geometry(char **meta, int z, unsigned tx, unsigned ty, int detail);
drawvec decode_geometry(char **meta, int z, unsigned tx, unsigned ty, int detail, long long *bbox);
void to_tile_scale(drawvec &geom, int z, int detail);
drawvec remove_noop(drawvec geom, int type);
drawvec clip_poly(drawvec &geom, int z, int detail, int buffer);
drawvec remove_noop(drawvec geom, int type, int shift);
drawvec clip_point(drawvec &geom, int z, int detail, long long buffer);
drawvec clean_or_clip_poly(drawvec &geom, int z, int detail, int buffer, bool clip);
drawvec reduce_tiny_poly(drawvec &geom, int z, int detail, bool *reduced, double *accum_area);
drawvec clip_lines(drawvec &geom, int z, int detail, long long buffer);
int quick_check(long long *bbox, int z, int detail, long long buffer);
drawvec simplify_lines(drawvec &geom, int z, int detail);
drawvec reorder_lines(drawvec &geom);
drawvec fix_polygon(drawvec &geom);

View File

@ -58,29 +58,22 @@ json_pull *json_begin_file(FILE *f) {
return json_begin(read_file, f);
}
#if 0
static int read_string(json_pull *j) {
static int read_string(json_pull *j, char *buffer, int n) {
char *cp = j->source;
if (*cp == '\0') {
return EOF;
}
int c = (unsigned char) *cp;
j->source = cp + 1;
return c;
}
int out = 0;
static int peek_string(json_pull *p) {
char *cp = p->source;
if (*cp == '\0') {
return EOF;
while (out < n && cp[out] != '\0') {
buffer[out] = cp[out];
out++;
}
return (unsigned char) *cp;
j->source = cp + out;
return out;
}
json_pull *json_begin_string(char *s) {
return json_begin(read_string, peek_string, s);
return json_begin(read_string, s);
}
#endif
void json_end(json_pull *p) {
free(p->buffer);
@ -254,7 +247,7 @@ again:
}
if (j->container->expect != JSON_COMMA) {
if (! (j->container->expect == JSON_ITEM && j->container->length == 0)) {
if (!(j->container->expect == JSON_ITEM && j->container->length == 0)) {
j->error = "Found ] without final element";
return NULL;
}
@ -292,7 +285,7 @@ again:
}
if (j->container->expect != JSON_COMMA) {
if (! (j->container->expect == JSON_KEY && j->container->length == 0)) {
if (!(j->container->expect == JSON_KEY && j->container->length == 0)) {
j->error = "Found } without final element";
return NULL;
}
@ -339,20 +332,17 @@ again:
/////////////////////////// Comma
if (c == ',') {
if (j->container == NULL) {
j->error = "Found comma at top level";
return NULL;
}
if (j->container != NULL) {
if (j->container->expect != JSON_COMMA) {
j->error = "Found unexpected comma";
return NULL;
}
if (j->container->expect != JSON_COMMA) {
j->error = "Found unexpected comma";
return NULL;
}
if (j->container->type == JSON_HASH) {
j->container->expect = JSON_KEY;
} else {
j->container->expect = JSON_ITEM;
if (j->container->type == JSON_HASH) {
j->container->expect = JSON_KEY;
} else {
j->container->expect = JSON_ITEM;
}
}
if (cb != NULL) {
@ -506,7 +496,6 @@ again:
json_object *s = add_object(j, JSON_STRING);
if (s != NULL) {
val.buf = realloc(val.buf, val.n + 1);
s->string = val.buf;
s->length = val.n;
} else {
@ -576,11 +565,19 @@ void json_free(json_object *o) {
free(o->string);
}
json_disconnect(o);
free(o);
}
void json_disconnect(json_object *o) {
// Expunge references to this as an array element
// or a hash key or value.
if (o->parent != NULL) {
if (o->parent->type == JSON_ARRAY) {
int i;
for (i = 0; i < o->parent->length; i++) {
if (o->parent->array[i] == o) {
break;
@ -594,6 +591,8 @@ void json_free(json_object *o) {
}
if (o->parent->type == JSON_HASH) {
int i;
for (i = 0; i < o->parent->length; i++) {
if (o->parent->keys[i] == o) {
o->parent->keys[i] = fabricate_object(o->parent, JSON_NULL);
@ -620,5 +619,5 @@ void json_free(json_object *o) {
}
}
free(o);
o->parent = NULL;
}

View File

@ -1,12 +1,21 @@
typedef enum json_type {
// These types can be returned by json_read()
JSON_HASH, JSON_ARRAY, JSON_NUMBER, JSON_STRING, JSON_TRUE, JSON_FALSE, JSON_NULL,
JSON_HASH,
JSON_ARRAY,
JSON_NUMBER,
JSON_STRING,
JSON_TRUE,
JSON_FALSE,
JSON_NULL,
// These and JSON_HASH and JSON_ARRAY can be called back by json_read_with_separators()
JSON_COMMA, JSON_COLON,
JSON_COMMA,
JSON_COLON,
// These are only used internally as expectations of what comes next
JSON_ITEM, JSON_KEY, JSON_VALUE,
JSON_ITEM,
JSON_KEY,
JSON_VALUE,
} json_type;
typedef struct json_object {
@ -39,10 +48,7 @@ typedef struct json_pull {
} json_pull;
json_pull *json_begin_file(FILE *f);
#if 0
json_pull *json_begin_string(char *s);
#endif
json_pull *json_begin(int (*read)(struct json_pull *, char *buffer, int n), void *source);
void json_end(json_pull *p);
@ -53,5 +59,6 @@ json_object *json_read_tree(json_pull *j);
json_object *json_read(json_pull *j);
json_object *json_read_separators(json_pull *j, json_separator_callback cb, void *state);
void json_free(json_object *j);
void json_disconnect(json_object *j);
json_object *json_hash_get(json_object *o, const char *s);

338
man/tippecanoe.1 Normal file
View File

@ -0,0 +1,338 @@
.TH tippecanoe
.PP
Builds vector tilesets
\[la]https://www.mapbox.com/developers/vector-tiles/\[ra] from large collections of GeoJSON
\[la]http://geojson.org/\[ra]
features. This is a tool for making maps from huge datasets
\[la]MADE_WITH.md\[ra]\&.
.SH Intent
.PP
The goal of Tippecanoe is to enable making a scale\-independent view of your data,
so that at any level from the entire world to a single building, you can see
the density and texture of the data rather than a simplification from dropping
supposedly unimportant features or clustering or aggregating them.
.PP
If you give it all of OpenStreetMap and zoom out, it should give you back
something that looks like "All Streets
\[la]http://benfry.com/allstreets/map5.html\[ra]"
rather than something that looks like an Interstate road atlas.
.PP
If you give it all the building footprints in Los Angeles and zoom out
far enough that most individual buildings are no longer discernable, you
should still be able to see the extent and variety of development in every neighborhood,
not just the largest downtown buildings.
.PP
If you give it a collection of years of tweet locations, you should be able to
see the shape and relative popularity of every point of interest and every
significant travel corridor.
.SH Installation
.PP
The easiest way to install tippecanoe on OSX is with Homebrew
\[la]http://brew.sh/\[ra]:
.PP
.RS
.nf
$ brew install tippecanoe
.fi
.RE
.SH Usage
.PP
.RS
.nf
$ tippecanoe \-o file.mbtiles [file.json ...]
.fi
.RE
.PP
If no files are specified, it reads GeoJSON from the standard input.
If multiple files are specified, each is placed in its own layer.
.PP
The GeoJSON features need not be wrapped in a FeatureCollection.
You can concatenate multiple GeoJSON features or files together,
and it will parse out the features and ignore whatever other objects
it encounters.
.SH Options
.SS Naming
.RS
.IP \(bu 2
\-l \fIname\fP: Layer name (default "file" if source is file.json or output is file.mbtiles). Only works if there is only one layer.
.IP \(bu 2
\-n \fIname\fP: Human\-readable name (default file.json)
.RE
.SS File control
.RS
.IP \(bu 2
\-o \fIfile\fP\&.mbtiles: Name the output file.
.IP \(bu 2
\-f: Delete the mbtiles file if it already exists instead of giving an error
.IP \(bu 2
\-t \fIdirectory\fP: Put the temporary files in \fIdirectory\fP\&.
.RE
.SS Zoom levels and resolution
.RS
.IP \(bu 2
\-z \fIzoom\fP: Base (maxzoom) zoom level (default 14)
.IP \(bu 2
\-Z \fIzoom\fP: Lowest (minzoom) zoom level (default 0)
.IP \(bu 2
\-d \fIdetail\fP: Detail at base zoom level (default 12 at \-z14 or higher, or 13 at \-z13 or lower. Detail beyond 13 has rendering problems with Mapbox GL.)
.IP \(bu 2
\-D \fIdetail\fP: Detail at lower zoom levels (default 10, for tile resolution of 1024)
.IP \(bu 2
\-m \fIdetail\fP: Minimum detail that it will try if tiles are too big at regular detail (default 7)
.IP \(bu 2
\-b \fIpixels\fP: Buffer size where features are duplicated from adjacent tiles. Units are "screen pixels"\-\-1/256th of the tile width or height. (default 5)
.RE
.SS Properties
.RS
.IP \(bu 2
\-x \fIname\fP: Exclude the named properties from all features
.IP \(bu 2
\-y \fIname\fP: Include the named properties in all features, excluding all those not explicitly named
.IP \(bu 2
\-X: Exclude all properties and encode only geometries
.RE
.SS Point simplification
.RS
.IP \(bu 2
\-r \fIrate\fP: Rate at which dots are dropped at lower zoom levels (default 2.5)
.IP \(bu 2
\-g \fIgamma\fP: Rate at which especially dense dots are dropped (default 0, for no effect). A gamma of 2 reduces the number of dots less than a pixel apart to the square root of their original number.
.RE
.SS Doing more
.RS
.IP \(bu 2
\-ac: Coalesce adjacent line and polygon features that have the same properties
.IP \(bu 2
\-ar: Try reversing the directions of lines to make them coalesce and compress better
.IP \(bu 2
\-ao: Reorder features to put ones with the same properties in sequence, to try to get them to coalesce
.IP \(bu 2
\-al: Let "dot" dropping at lower zooms apply to lines too
.RE
.SS Doing less
.RS
.IP \(bu 2
\-ps: Don't simplify lines
.IP \(bu 2
\-pf: Don't limit tiles to 200,000 features
.IP \(bu 2
\-pk: Don't limit tiles to 500K bytes
.IP \(bu 2
\-pd: Dynamically drop some fraction of features from large tiles to keep them under the 500K size limit. It will probably look ugly at the tile boundaries.
.IP \(bu 2
\-pi: Preserve the original input order of features as the drawing order instead of ordering geographically. (This is implemented as a restoration of the original order at the end, so that dot\-dropping is still geographic, which means it also undoes \-ao).
.IP \(bu 2
\-q: Work quietly instead of reporting progress
.RE
.SH Example
.PP
.RS
.nf
$ tippecanoe \-o alameda.mbtiles \-l alameda \-n "Alameda County from TIGER" \-z13 tl_2014_06001_roads.json
.fi
.RE
.PP
.RS
.nf
$ cat tiger/tl_2014_*_roads.json | tippecanoe \-o tiger.mbtiles \-l roads \-n "All TIGER roads, one zoom" \-z12 \-Z12 \-d14 \-x LINEARID \-x RTTYP
.fi
.RE
.SH GeoJSON extension
.PP
Tippecanoe defines a GeoJSON extension that you can use to specify the minimum and/or maximum zoom level
at which an individual feature will be included in the vector tile dataset being produced.
If you have a feature like this:
.PP
.RS
.nf
{
"type" : "Feature",
"tippecanoe" : { "maxzoom" : 9, "minzoom" : 4 },
"properties" : { "FULLNAME" : "N Vasco Rd" },
"geometry" : {
"type" : "LineString",
"coordinates" : [ [ \-121.733350, 37.767671 ], [ \-121.733600, 37.767483 ], [ \-121.733131, 37.766952 ] ]
}
}
.fi
.RE
.PP
with a \fB\fCtippecanoe\fR object specifiying a \fB\fCmaxzoom\fR of 9 and a \fB\fCminzoom\fR of 4, the feature
will only appear in the vector tiles for zoom levels 4 through 9. Note that the \fB\fCtippecanoe\fR
object belongs to the Feature, not to its \fB\fCproperties\fR\&.
.SH Point styling
.PP
To provide a consistent density gradient as you zoom, the Mapbox Studio style needs to be
coordinated with the base zoom level and dot\-dropping rate. You can use this shell script to
calculate the appropriate marker\-width at high zoom levels to match the fraction of dots
that were dropped at low zoom levels.
.PP
If you used \fB\fC\-z\fR to change the base zoom level or \fB\fC\-r\fR to change the
dot\-dropping rate, replace them in the \fB\fCbasezoom\fR and \fB\fCrate\fR below.
.PP
.RS
.nf
awk 'BEGIN {
dotsize = 2; # up to you to decide
basezoom = 14; # tippecanoe \-z 14
rate = 2.5; # tippecanoe \-r 2.5
print " marker\-line\-width: 0;";
print " marker\-ignore\-placement: true;";
print " marker\-allow\-overlap: true;";
print " marker\-width: " dotsize ";";
for (i = basezoom + 1; i <= 22; i++) {
print " [zoom >= " i "] { marker\-width: " (dotsize * exp(log(sqrt(rate)) * (i \- basezoom))) "; }";
}
exit(0);
}'
.fi
.RE
.SH Geometric simplifications
.PP
At every zoom level, line and polygon features are subjected to Douglas\-Peucker
simplification to the resolution of the tile.
.PP
For point features, it drops 1/2.5 of the dots for each zoom level above the base.
I don't know why 2.5 is the appropriate number, but the densities of many different
data sets fall off at about this same rate. You can use \-r to specify a different rate.
.PP
You can use the gamma option to thin out especially dense clusters of points.
For any area where dots are closer than one pixel together (at whatever zoom level),
a gamma of 3, for example, will reduce these clusters to the cube root of their original density.
.PP
For line features, it drops any features that are too small to draw at all.
This still leaves the lower zooms too dark (and too dense for the 500K tile limit,
in some places), so I need to figure out an equitable way to throw features away.
.PP
Any polygons that are smaller than a minimum area (currently 4 square subpixels) will
have their probability diffused, so that some of them will be drawn as a square of
this minimum size and others will not be drawn at all, preserving the total area that
all of them should have had together.
.PP
Features in the same tile that share the same type and attributes are coalesced
together into a single geometry. You are strongly encouraged to use \-x to exclude
any unnecessary properties to reduce wasted file size.
.PP
If a tile is larger than 500K, it will try encoding that tile at progressively
lower resolutions before failing if it still doesn't fit.
.SH Development
.PP
Requires protoc and sqlite3. Rebuilding the manpage
uses md2man (\fB\fCgem install md2man\fR).
.PP
MacOS:
.PP
.RS
.nf
brew install protobuf
.fi
.RE
.PP
Linux:
.PP
.RS
.nf
sudo apt\-get install libprotobuf\-dev
sudo apt\-get install protobuf\-compiler
sudo apt\-get install libsqlite3\-dev
.fi
.RE
.PP
Then build:
.PP
.RS
.nf
make
.fi
.RE
.PP
and perhaps
.PP
.RS
.nf
make install
.fi
.RE
.SH Examples
.PP
Check out some examples of maps made with tippecanoe
\[la]MADE_WITH.md\[ra]
.SH Name
.PP
The name is a joking reference
\[la]http://en.wikipedia.org/wiki/Tippecanoe_and_Tyler_Too\[ra] to a "tiler" for making map tiles.
.SH tile\-join
.PP
Tile\-join is a tool for joining new attributes from a CSV file to features that
have already been tiled with tippecanoe. It reads the tiles from an existing .mbtiles
file, matches them against the records of the CSV, and writes out a new tileset.
.PP
The options are:
.RS
.IP \(bu 2
\-o \fIout.mbtiles\fP: Write the new tiles to the specified .mbtiles file
.IP \(bu 2
\-f: Remove \fIout.mbtiles\fP if it already exists
.IP \(bu 2
\-c \fImatch.csv\fP: Use \fImatch.csv\fP as the source for new attributes to join to the features. The first line of the file should be the key names; the other lines are values. The first column is the one to match against the existing features; the other columns are the new data to add.
.IP \(bu 2
\-x \fIkey\fP: Remove attributes of type \fIkey\fP from the output. You can use this to remove the field you are matching against if you no longer need it after joining, or to remove any other attributes you don't want.
.IP \(bu 2
\-i: Only include features that matched the CSV.
.RE
.PP
Because tile\-join just copies the geometries to the new .mbtiles without processing them,
it doesn't have any of tippecanoe's recourses if the new tiles are bigger than the 500K tile limit.
If a tile is too big, it is just left out of the new tileset.
.SH Example
.PP
Imagine you have a tileset of census blocks:
.PP
.RS
.nf
curl \-O http://www2.census.gov/geo/tiger/TIGER2010/TABBLOCK/2010/tl_2010_06001_tabblock10.zip
unzip tl_2010_06001_tabblock10.zip
ogr2ogr \-f GeoJSON tl_2010_06001_tabblock10.json tl_2010_06001_tabblock10.shp
\&./tippecanoe \-o tl_2010_06001_tabblock10.mbtiles tl_2010_06001_tabblock10.json
.fi
.RE
.PP
and a CSV of their populations:
.PP
.RS
.nf
curl \-O http://www2.census.gov/census_2010/01\-Redistricting_File\-\-PL_94\-171/California/ca2010.pl.zip
unzip \-p ca2010.pl.zip cageo2010.pl |
awk 'BEGIN {
print "GEOID10,population"
}
(substr($0, 9, 3) == "750") {
print "\\"" substr($0, 28, 2) substr($0, 30, 3) substr($0, 55, 6) substr($0, 62, 4) "\\"," (0 + substr($0, 328, 9))
}' > population.csv
.fi
.RE
.PP
which looks like this:
.PP
.RS
.nf
GEOID10,population
"060014277003018",0
"060014283014046",0
"060014284001020",0
\&...
"060014507501001",202
"060014507501002",119
"060014507501003",193
"060014507501004",85
\&...
.fi
.RE
.PP
Then you can join those populations to the geometries and discard the no\-longer\-needed ID field:
.PP
.RS
.nf
\&./tile\-join \-o population.mbtiles \-x GEOID10 \-c population.csv tl_2010_06001_tabblock10.mbtiles
.fi
.RE

View File

@ -12,7 +12,7 @@ sqlite3 *mbtiles_open(char *dbname, char **argv) {
sqlite3 *outdb;
if (sqlite3_open(dbname, &outdb) != SQLITE_OK) {
fprintf(stderr, "%s: %s: %s\n", argv[0], dbname, sqlite3_errmsg(outdb));
fprintf(stderr, "%s: %s: %s\n", argv[0], dbname, sqlite3_errmsg(outdb));
exit(EXIT_FAILURE);
}
@ -75,19 +75,25 @@ static void quote(char **buf, const char *s) {
char *out = tmp;
for (; *s != '\0'; s++) {
if (*s == '\\' || *s == '\"') {
unsigned char ch = (unsigned char) *s;
if (ch == '\\' || ch == '\"') {
*out++ = '\\';
*out++ = *s;
} else if (*s < ' ') {
sprintf(out, "\\u%04x", *s);
*out++ = ch;
} else if (ch < ' ') {
sprintf(out, "\\u%04x", ch);
out = out + strlen(out);
} else {
*out++ = *s;
*out++ = ch;
}
}
*out = '\0';
*buf = realloc(*buf, strlen(*buf) + strlen(tmp) + 1);
if (*buf == NULL) {
perror("realloc");
exit(EXIT_FAILURE);
}
strcat(*buf, tmp);
}
@ -107,7 +113,7 @@ static void aprintf(char **buf, const char *format, ...) {
free(tmp);
}
void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, const char *layername, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, struct pool *fields) {
void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, char **layername, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, struct pool **file_keys, int nlayers) {
char *sql, *err;
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('name', %Q);", fname);
@ -174,27 +180,40 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, const char *layer
sqlite3_free(sql);
char *buf = strdup("{");
aprintf(&buf, "\"vector_layers\": [ { \"id\": \"");
quote(&buf, layername);
aprintf(&buf, "\", \"description\": \"\", \"minzoom\": %d, \"maxzoom\": %d, \"fields\": {", minzoom, maxzoom);
aprintf(&buf, "\"vector_layers\": [ ");
struct pool_val *pv;
for (pv = fields->head; pv != NULL; pv = pv->next) {
aprintf(&buf, "\"");
quote(&buf, pv->s);
if (pv->type == VT_NUMBER) {
aprintf(&buf, "\": \"Number\"");
} else {
aprintf(&buf, "\": \"String\"");
}
if (pv->next != NULL) {
int i;
for (i = 0; i < nlayers; i++) {
if (i != 0) {
aprintf(&buf, ", ");
}
aprintf(&buf, "{ \"id\": \"");
quote(&buf, layername[i]);
aprintf(&buf, "\", \"description\": \"\", \"minzoom\": %d, \"maxzoom\": %d, \"fields\": {", minzoom, maxzoom);
struct pool_val *pv;
for (pv = file_keys[i]->head; pv != NULL; pv = pv->next) {
aprintf(&buf, "\"");
quote(&buf, pv->s);
if (pv->type == VT_NUMBER) {
aprintf(&buf, "\": \"Number\"");
} else if (pv->type == VT_BOOLEAN) {
aprintf(&buf, "\": \"Boolean\"");
} else {
aprintf(&buf, "\": \"String\"");
}
if (pv->next != NULL) {
aprintf(&buf, ", ");
}
}
aprintf(&buf, "} }");
}
aprintf(&buf, "} } ] }");
aprintf(&buf, " ] }");
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('json', %Q);", buf);
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
@ -209,11 +228,7 @@ void mbtiles_close(sqlite3 *outdb, char **argv) {
char *err;
if (sqlite3_exec(outdb, "ANALYZE;", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: index metadata: %s\n", argv[0], err);
exit(EXIT_FAILURE);
}
if (sqlite3_exec(outdb, "VACUUM;", NULL, NULL, &err) != SQLITE_OK) {
fprintf(stderr, "%s: index tiles: %s\n", argv[0], err);
fprintf(stderr, "%s: ANALYZE failed: %s\n", argv[0], err);
exit(EXIT_FAILURE);
}
if (sqlite3_close(outdb) != SQLITE_OK) {

View File

@ -2,6 +2,6 @@ sqlite3 *mbtiles_open(char *dbname, char **argv);
void mbtiles_write_tile(sqlite3 *outdb, int z, int tx, int ty, const char *data, int size);
void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, const char *layername, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, struct pool *fields);
void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, char **layername, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, struct pool **file_keys, int nlayers);
void mbtiles_close(sqlite3 *outdb, char **argv);

69
memfile.c Normal file
View File

@ -0,0 +1,69 @@
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include "memfile.h"
#define INCREMENT 131072
struct memfile *memfile_open(int fd) {
if (ftruncate(fd, INCREMENT) != 0) {
return NULL;
}
char *map = mmap(NULL, INCREMENT, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map == MAP_FAILED) {
return NULL;
}
struct memfile *mf = malloc(sizeof(struct memfile));
if (mf == NULL) {
munmap(map, INCREMENT);
return NULL;
}
mf->fd = fd;
mf->map = map;
mf->len = INCREMENT;
mf->off = 0;
return mf;
}
int memfile_close(struct memfile *file) {
if (munmap(file->map, file->len) != 0) {
return -1;
}
if (file->fd >= 0) {
if (close(file->fd) != 0) {
return -1;
}
}
free(file);
return 0;
}
int memfile_write(struct memfile *file, void *s, long long len) {
if (file->off + len > file->len) {
if (munmap(file->map, file->len) != 0) {
return -1;
}
file->len += INCREMENT;
if (ftruncate(file->fd, file->len) != 0) {
return -1;
}
file->map = mmap(NULL, file->len, PROT_READ | PROT_WRITE, MAP_SHARED, file->fd, 0);
if (file->map == MAP_FAILED) {
return -1;
}
}
memcpy(file->map + file->off, s, len);
file->off += len;
return len;
}

10
memfile.h Normal file
View File

@ -0,0 +1,10 @@
struct memfile {
int fd;
char *map;
long long len;
long long off;
};
struct memfile *memfile_open(int fd);
int memfile_close(struct memfile *file);
int memfile_write(struct memfile *file, void *s, long long len);

64
pool.c
View File

@ -1,12 +1,25 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pool.h"
static struct pool_val *pool1(struct pool *p, char *s, int type, int (*compare)(const char *, const char *)) {
struct pool_val **v = &(p->vals);
#define POOL_WIDTH 256
static int hash(const char *s) {
int h = 0;
for (; *s; s++) {
h = h * 37 + *s;
}
h = h & 0xFF;
return h;
}
struct pool_val *pool(struct pool *p, const char *s, int type) {
int h = hash(s);
struct pool_val **v = &(p->vals[h]);
while (*v != NULL) {
int cmp = compare(s, (*v)->s);
int cmp = strcmp(s, (*v)->s);
if (cmp == 0) {
cmp = type - (*v)->type;
@ -21,27 +34,33 @@ static struct pool_val *pool1(struct pool *p, char *s, int type, int (*compare)(
}
}
*v = malloc(sizeof(struct pool_val));
(*v)->left = NULL;
(*v)->right = NULL;
(*v)->next = NULL;
(*v)->s = s;
(*v)->type = type;
(*v)->n = p->n++;
struct pool_val *nv = malloc(sizeof(struct pool_val));
if (nv == NULL) {
fprintf(stderr, "out of memory making string pool\n");
exit(EXIT_FAILURE);
}
nv->left = NULL;
nv->right = NULL;
nv->next = NULL;
nv->s = s;
nv->type = type;
nv->n = p->n++;
if (p->tail != NULL) {
p->tail->next = *v;
p->tail->next = nv;
}
p->tail = *v;
p->tail = nv;
if (p->head == NULL) {
p->head = *v;
p->head = nv;
}
*v = nv;
return *v;
}
int is_pooled(struct pool *p, char *s, int type) {
struct pool_val **v = &(p->vals);
int is_pooled(struct pool *p, const char *s, int type) {
int h = hash(s);
struct pool_val **v = &(p->vals[h]);
while (*v != NULL) {
int cmp = strcmp(s, (*v)->s);
@ -62,15 +81,10 @@ int is_pooled(struct pool *p, char *s, int type) {
return 0;
}
struct pool_val *pool(struct pool *p, char *s, int type) {
return pool1(p, s, type, strcmp);
}
void pool_free1(struct pool *p, void (*func)(void *)) {
while (p->head != NULL) {
if (func != NULL) {
func(p->head->s);
func((void *) p->head->s);
}
struct pool_val *next = p->head->next;
@ -80,6 +94,8 @@ void pool_free1(struct pool *p, void (*func)(void *)) {
p->head = NULL;
p->tail = NULL;
free(p->vals);
p->vals = NULL;
}
@ -93,7 +109,11 @@ void pool_free_strings(struct pool *p) {
void pool_init(struct pool *p, int n) {
p->n = n;
p->vals = NULL;
p->vals = calloc(POOL_WIDTH, sizeof(struct pool_val *));
if (p->vals == NULL) {
fprintf(stderr, "out of memory creating string pool\n");
exit(EXIT_FAILURE);
}
p->head = NULL;
p->tail = NULL;
}

9
pool.h
View File

@ -1,5 +1,5 @@
struct pool_val {
char *s;
const char *s;
int type;
int n;
@ -10,16 +10,15 @@ struct pool_val {
};
struct pool {
struct pool_val *vals;
struct pool_val **vals;
struct pool_val *head;
struct pool_val *tail;
int n;
};
struct pool_val *pool(struct pool *p, char *s, int type);
struct pool_val *pool(struct pool *p, const char *s, int type);
void pool_free(struct pool *p);
void pool_free_strings(struct pool *p);
void pool_init(struct pool *p, int n);
int is_pooled(struct pool *p, char *s, int type);
int is_pooled(struct pool *p, const char *s, int type);

View File

@ -7,7 +7,7 @@ void latlon2tile(double lat, double lon, int zoom, unsigned int *x, unsigned int
unsigned long long n = 1LL << zoom;
long long llx = n * ((lon + 180) / 360);
long long lly = n * (1 - (log(tan(lat_rad) + 1/cos(lat_rad)) / M_PI)) / 2;
long long lly = n * (1 - (log(tan(lat_rad) + 1 / cos(lat_rad)) / M_PI)) / 2;
if (lat >= 85.0511) {
lly = 0;
@ -37,8 +37,7 @@ void latlon2tile(double lat, double lon, int zoom, unsigned int *x, unsigned int
void tile2latlon(unsigned int x, unsigned int y, int zoom, double *lat, double *lon) {
unsigned long long n = 1LL << zoom;
*lon = 360.0 * x / n - 180.0;
float lat_rad = atan(sinh(M_PI * (1 - 2.0 * y / n)));
*lat = lat_rad * 180 / M_PI;
*lat = atan(sinh(M_PI * (1 - 2.0 * y / n))) * 180.0 / M_PI;
}
unsigned long long encode(unsigned int wx, unsigned int wy) {
@ -53,7 +52,6 @@ unsigned long long encode(unsigned int wx, unsigned int wy) {
out |= v;
}
return out;
}

547
tile-join.cc Normal file
View File

@ -0,0 +1,547 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sqlite3.h>
#include <vector>
#include <string>
#include <map>
#include <zlib.h>
#include <math.h>
#include "vector_tile.pb.h"
#include "tile.h"
extern "C" {
#include "projection.h"
#include "pool.h"
#include "mbtiles.h"
}
std::string dequote(std::string s);
struct stats {
int minzoom;
int maxzoom;
double midlat, midlon;
double minlat, minlon, maxlat, maxlon;
};
// https://github.com/mapbox/mapnik-vector-tile/blob/master/src/vector_tile_compression.hpp
inline bool is_compressed(std::string const &data) {
return data.size() > 2 && (((uint8_t) data[0] == 0x78 && (uint8_t) data[1] == 0x9C) || ((uint8_t) data[0] == 0x1F && (uint8_t) data[1] == 0x8B));
}
// https://github.com/mapbox/mapnik-vector-tile/blob/master/src/vector_tile_compression.hpp
inline int decompress(std::string const &input, std::string &output) {
z_stream inflate_s;
inflate_s.zalloc = Z_NULL;
inflate_s.zfree = Z_NULL;
inflate_s.opaque = Z_NULL;
inflate_s.avail_in = 0;
inflate_s.next_in = Z_NULL;
if (inflateInit2(&inflate_s, 32 + 15) != Z_OK) {
fprintf(stderr, "error: %s\n", inflate_s.msg);
}
inflate_s.next_in = (Bytef *) input.data();
inflate_s.avail_in = input.size();
size_t length = 0;
do {
output.resize(length + 2 * input.size());
inflate_s.avail_out = 2 * input.size();
inflate_s.next_out = (Bytef *) (output.data() + length);
int ret = inflate(&inflate_s, Z_FINISH);
if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR) {
fprintf(stderr, "error: %s\n", inflate_s.msg);
return 0;
}
length += (2 * input.size() - inflate_s.avail_out);
} while (inflate_s.avail_out == 0);
inflateEnd(&inflate_s);
output.resize(length);
return 1;
}
// https://github.com/mapbox/mapnik-vector-tile/blob/master/src/vector_tile_compression.hpp
static inline int compress(std::string const &input, std::string &output) {
z_stream deflate_s;
deflate_s.zalloc = Z_NULL;
deflate_s.zfree = Z_NULL;
deflate_s.opaque = Z_NULL;
deflate_s.avail_in = 0;
deflate_s.next_in = Z_NULL;
deflateInit2(&deflate_s, Z_BEST_COMPRESSION, Z_DEFLATED, 31, 8, Z_DEFAULT_STRATEGY);
deflate_s.next_in = (Bytef *) input.data();
deflate_s.avail_in = input.size();
size_t length = 0;
do {
size_t increase = input.size() / 2 + 1024;
output.resize(length + increase);
deflate_s.avail_out = increase;
deflate_s.next_out = (Bytef *) (output.data() + length);
int ret = deflate(&deflate_s, Z_FINISH);
if (ret != Z_STREAM_END && ret != Z_OK && ret != Z_BUF_ERROR) {
return -1;
}
length += (increase - deflate_s.avail_out);
} while (deflate_s.avail_out == 0);
deflateEnd(&deflate_s);
output.resize(length);
return 0;
}
void handle(std::string message, int z, unsigned x, unsigned y, struct pool **file_keys, char ***layernames, int *nlayers, sqlite3 *outdb, std::vector<std::string> &header, std::map<std::string, std::vector<std::string> > &mapping, struct pool *exclude, int ifmatched) {
GOOGLE_PROTOBUF_VERIFY_VERSION;
// https://github.com/mapbox/mapnik-vector-tile/blob/master/examples/c%2B%2B/tileinfo.cpp
mapnik::vector::tile tile;
mapnik::vector::tile outtile;
int features_added = 0;
if (is_compressed(message)) {
std::string uncompressed;
decompress(message, uncompressed);
if (!tile.ParseFromString(uncompressed)) {
fprintf(stderr, "Couldn't decompress tile %d/%u/%u\n", z, x, y);
exit(EXIT_FAILURE);
}
} else if (!tile.ParseFromString(message)) {
fprintf(stderr, "Couldn't parse tile %d/%u/%u\n", z, x, y);
exit(EXIT_FAILURE);
}
for (int l = 0; l < tile.layers_size(); l++) {
mapnik::vector::tile_layer layer = tile.layers(l);
mapnik::vector::tile_layer *outlayer = outtile.add_layers();
outlayer->set_name(layer.name());
outlayer->set_version(layer.version());
outlayer->set_extent(layer.extent());
const char *ln = layer.name().c_str();
int ll;
for (ll = 0; ll < *nlayers; ll++) {
if (strcmp((*layernames)[ll], ln) == 0) {
break;
}
}
if (ll == *nlayers) {
*file_keys = (struct pool *) realloc(*file_keys, (ll + 1) * sizeof(struct pool));
*layernames = (char **) realloc(*layernames, (ll + 1) * sizeof(char *));
if (*file_keys == NULL) {
perror("realloc file_keys");
exit(EXIT_FAILURE);
}
if (*layernames == NULL) {
perror("realloc layernames");
exit(EXIT_FAILURE);
}
pool_init(&((*file_keys)[ll]), 0);
(*layernames)[ll] = strdup(ln);
*nlayers = ll + 1;
}
struct pool keys, values;
pool_init(&keys, 0);
pool_init(&values, 0);
for (int f = 0; f < layer.features_size(); f++) {
mapnik::vector::tile_feature feat = layer.features(f);
std::vector<int> feature_tags;
int matched = 0;
for (int t = 0; t + 1 < feat.tags_size(); t += 2) {
const char *key = layer.keys(feat.tags(t)).c_str();
mapnik::vector::tile_value const &val = layer.values(feat.tags(t + 1));
char *value;
int type = -1;
if (val.has_string_value()) {
value = strdup(val.string_value().c_str());
type = VT_STRING;
} else if (val.has_int_value()) {
if (asprintf(&value, "%lld", (long long) val.int_value()) >= 0) {
type = VT_NUMBER;
}
} else if (val.has_double_value()) {
if (asprintf(&value, "%g", val.double_value()) >= 0) {
type = VT_NUMBER;
}
} else if (val.has_float_value()) {
if (asprintf(&value, "%g", val.float_value()) >= 0) {
type = VT_NUMBER;
}
} else if (val.has_bool_value()) {
if (asprintf(&value, "%s", val.bool_value() ? "true" : "false") >= 0) {
type = VT_BOOLEAN;
}
} else if (val.has_sint_value()) {
if (asprintf(&value, "%lld", (long long) val.sint_value()) >= 0) {
type = VT_NUMBER;
}
} else if (val.has_uint_value()) {
if (asprintf(&value, "%llu", (long long) val.uint_value()) >= 0) {
type = VT_NUMBER;
}
} else {
continue;
}
if (type < 0) {
continue;
}
if (!is_pooled(exclude, key, VT_STRING)) {
if (!is_pooled(&((*file_keys)[ll]), key, type)) {
pool(&((*file_keys)[ll]), strdup(key), type);
}
struct pool_val *k, *v;
if (is_pooled(&keys, key, VT_STRING)) {
k = pool(&keys, key, VT_STRING);
} else {
k = pool(&keys, strdup(key), VT_STRING);
}
if (is_pooled(&values, value, type)) {
v = pool(&values, value, type);
} else {
v = pool(&values, strdup(value), type);
}
feature_tags.push_back(k->n);
feature_tags.push_back(v->n);
}
if (strcmp(key, header[0].c_str()) == 0) {
std::map<std::string, std::vector<std::string> >::iterator ii = mapping.find(std::string(value));
if (ii != mapping.end()) {
std::vector<std::string> fields = ii->second;
matched = 1;
for (unsigned i = 1; i < fields.size(); i++) {
std::string joinkey = header[i];
std::string joinval = fields[i];
int type = VT_STRING;
if (joinval.size() > 0) {
if (joinval[0] == '"') {
joinval = dequote(joinval);
} else if ((joinval[0] >= '0' && joinval[0] <= '9') || joinval[0] == '-') {
type = VT_NUMBER;
}
}
const char *sjoinkey = joinkey.c_str();
const char *sjoinval = joinval.c_str();
if (!is_pooled(exclude, sjoinkey, VT_STRING)) {
if (!is_pooled(&((*file_keys)[ll]), sjoinkey, type)) {
pool(&((*file_keys)[ll]), strdup(sjoinkey), type);
}
struct pool_val *k, *v;
if (is_pooled(&keys, sjoinkey, VT_STRING)) {
k = pool(&keys, sjoinkey, VT_STRING);
} else {
k = pool(&keys, strdup(sjoinkey), VT_STRING);
}
if (is_pooled(&values, sjoinval, type)) {
v = pool(&values, sjoinval, type);
} else {
v = pool(&values, strdup(sjoinval), type);
}
feature_tags.push_back(k->n);
feature_tags.push_back(v->n);
}
}
}
}
free(value);
}
if (matched || !ifmatched) {
mapnik::vector::tile_feature *outfeature = outlayer->add_features();
outfeature->set_type(feat.type());
for (int g = 0; g < feat.geometry_size(); g++) {
outfeature->add_geometry(feat.geometry(g));
}
for (unsigned i = 0; i < feature_tags.size(); i++) {
outfeature->add_tags(feature_tags[i]);
}
features_added++;
}
}
struct pool_val *pv;
for (pv = keys.head; pv != NULL; pv = pv->next) {
outlayer->add_keys(pv->s, strlen(pv->s));
}
for (pv = values.head; pv != NULL; pv = pv->next) {
mapnik::vector::tile_value *tv = outlayer->add_values();
if (pv->type == VT_NUMBER) {
tv->set_double_value(atof(pv->s));
} else if (pv->type == VT_BOOLEAN) {
tv->set_bool_value(pv->s[0] == 't');
} else {
tv->set_string_value(pv->s);
}
}
pool_free_strings(&keys);
pool_free_strings(&values);
}
if (features_added == 0) {
return;
}
std::string s;
std::string compressed;
outtile.SerializeToString(&s);
compress(s, compressed);
if (compressed.size() > 500000) {
fprintf(stderr, "Tile %d/%u/%u size is %lld, >500000. Skipping this tile\n.", z, x, y, (long long) compressed.size());
return;
}
mbtiles_write_tile(outdb, z, x, y, compressed.data(), compressed.size());
}
void decode(char *fname, char *map, struct pool **file_keys, char ***layernames, int *nlayers, sqlite3 *outdb, struct stats *st, std::vector<std::string> &header, std::map<std::string, std::vector<std::string> > &mapping, struct pool *exclude, int ifmatched) {
sqlite3 *db;
if (sqlite3_open(fname, &db) != SQLITE_OK) {
fprintf(stderr, "%s: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
const char *sql = "SELECT zoom_level, tile_column, tile_row, tile_data from tiles;";
sqlite3_stmt *stmt;
if (sqlite3_prepare_v2(db, sql, -1, &stmt, NULL) != SQLITE_OK) {
fprintf(stderr, "%s: select failed: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
while (sqlite3_step(stmt) == SQLITE_ROW) {
long long zoom = sqlite3_column_int(stmt, 0);
long long x = sqlite3_column_int(stmt, 1);
long long y = sqlite3_column_int(stmt, 2);
y = (1LL << zoom) - 1 - y;
int len = sqlite3_column_bytes(stmt, 3);
const char *s = (const char *) sqlite3_column_blob(stmt, 3);
fprintf(stderr, "%lld/%lld/%lld \r", zoom, x, y);
handle(std::string(s, len), zoom, x, y, file_keys, layernames, nlayers, outdb, header, mapping, exclude, ifmatched);
}
sqlite3_finalize(stmt);
if (sqlite3_prepare_v2(db, "SELECT value from metadata where name = 'minzoom'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
st->minzoom = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
}
if (sqlite3_prepare_v2(db, "SELECT value from metadata where name = 'maxzoom'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
st->maxzoom = sqlite3_column_int(stmt, 0);
}
sqlite3_finalize(stmt);
}
if (sqlite3_prepare_v2(db, "SELECT value from metadata where name = 'center'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
const unsigned char *s = sqlite3_column_text(stmt, 0);
sscanf((char *) s, "%lf,%lf", &st->midlon, &st->midlat);
}
sqlite3_finalize(stmt);
}
if (sqlite3_prepare_v2(db, "SELECT value from metadata where name = 'bounds'", -1, &stmt, NULL) == SQLITE_OK) {
if (sqlite3_step(stmt) == SQLITE_ROW) {
const unsigned char *s = sqlite3_column_text(stmt, 0);
sscanf((char *) s, "%lf,%lf,%lf,%lf", &st->minlon, &st->minlat, &st->maxlon, &st->maxlat);
}
sqlite3_finalize(stmt);
}
if (sqlite3_close(db) != SQLITE_OK) {
fprintf(stderr, "%s: could not close database: %s\n", fname, sqlite3_errmsg(db));
exit(EXIT_FAILURE);
}
}
void usage(char **argv) {
fprintf(stderr, "Usage: %s [-f] [-i] [-c joins.csv] [-x exclude ...] -o new.mbtiles source.mbtiles\n", argv[0]);
exit(EXIT_FAILURE);
}
#define MAXLINE 10000 /* XXX */
std::vector<std::string> split(char *s) {
std::vector<std::string> ret;
while (*s && *s != '\n') {
char *start = s;
int within = 0;
for (; *s && *s != '\n'; s++) {
if (*s == '"') {
within = !within;
}
if (*s == ',' && !within) {
break;
}
}
std::string v = std::string(start, s - start);
ret.push_back(v);
if (*s == ',') {
s++;
}
}
return ret;
}
std::string dequote(std::string s) {
std::string out;
unsigned i;
for (i = 0; i < s.size(); i++) {
if (s[i] == '"') {
if (i + 1 < s.size() && s[i + 1] == '"') {
out.push_back('"');
}
} else {
out.push_back(s[i]);
}
}
return out;
}
void readcsv(char *fn, std::vector<std::string> &header, std::map<std::string, std::vector<std::string> > &mapping) {
FILE *f = fopen(fn, "r");
if (f == NULL) {
perror(fn);
exit(EXIT_FAILURE);
}
char s[MAXLINE];
if (fgets(s, MAXLINE, f)) {
header = split(s);
for (unsigned i = 0; i < header.size(); i++) {
header[i] = dequote(header[i]);
}
}
while (fgets(s, MAXLINE, f)) {
std::vector<std::string> line = split(s);
if (line.size() > 0) {
line[0] = dequote(line[0]);
}
for (unsigned i = 0; i < line.size() && i < header.size(); i++) {
// printf("putting %s\n", line[0].c_str());
mapping.insert(std::pair<std::string, std::vector<std::string> >(line[0], line));
}
}
fclose(f);
}
int main(int argc, char **argv) {
char *outfile = NULL;
char *csv = NULL;
int force = 0;
int ifmatched = 0;
std::vector<std::string> header;
std::map<std::string, std::vector<std::string> > mapping;
struct pool exclude;
pool_init(&exclude, 0);
extern int optind;
extern char *optarg;
int i;
while ((i = getopt(argc, argv, "fo:c:x:i")) != -1) {
switch (i) {
case 'o':
outfile = optarg;
break;
case 'f':
force = 1;
break;
case 'i':
ifmatched = 1;
break;
case 'c':
if (csv != NULL) {
fprintf(stderr, "Only one -c for now\n");
exit(EXIT_FAILURE);
}
csv = optarg;
readcsv(csv, header, mapping);
break;
case 'x':
pool(&exclude, optarg, VT_STRING);
break;
default:
usage(argv);
}
}
if (argc - optind != 1 || outfile == NULL) {
usage(argv);
}
if (force) {
unlink(outfile);
}
sqlite3 *outdb = mbtiles_open(outfile, argv);
struct stats st;
memset(&st, 0, sizeof(st));
struct pool *file_keys = NULL;
char **layernames = NULL;
int nlayers = 0;
for (i = optind; i < argc; i++) {
decode(argv[i], csv, &file_keys, &layernames, &nlayers, outdb, &st, header, mapping, &exclude, ifmatched);
}
struct pool *fk[nlayers];
for (i = 0; i < nlayers; i++) {
fk[i] = &(file_keys[i]);
}
mbtiles_write_metadata(outdb, outfile, layernames, st.minzoom, st.maxzoom, st.minlat, st.minlon, st.maxlat, st.maxlon, st.midlat, st.midlon, fk, nlayers);
mbtiles_close(outdb, argv);
return 0;
}

945
tile.cc

File diff suppressed because it is too large Load Diff

24
tile.h
View File

@ -13,18 +13,24 @@
struct pool;
void serialize_int(FILE *out, int n, long long *fpos, const char *fname);
void serialize_long_long(FILE *out, long long n, long long *fpos, const char *fname);
void serialize_byte(FILE *out, signed char n, long long *fpos, const char *fname);
void serialize_uint(FILE *out, unsigned n, long long *fpos, const char *fname);
void serialize_string(FILE *out, const char *s, long long *fpos, const char *fname);
void deserialize_int(char **f, int *n);
void deserialize_long_long(char **f, long long *n);
void deserialize_uint(char **f, unsigned *n);
void deserialize_byte(char **f, signed char *n);
struct pool_val *deserialize_string(char **f, struct pool *p, int type);
long long write_tile(char **geom, char *metabase, char *stringpool, unsigned *file_bbox, int z, unsigned x, unsigned y, int detail, int min_detail, int basezoom, struct pool **file_keys, char **layernames, sqlite3 *outdb, double droprate, int buffer, const char *fname, FILE **geomfile, int file_minzoom, int file_maxzoom, double todo, char *geomstart, long long along, double gamma, int nlayers, char *prevent, char *additional);
struct index {
unsigned long long index;
long long fpos : 44;
int maxzoom : 6;
int minzoom : 6;
int type : 7;
int candup : 1;
};
int traverse_zooms(int *geomfd, off_t *geom_size, char *metabase, char *stringpool, struct pool **file_keys, unsigned *midx, unsigned *midy, char **layernames, int maxzoom, int minzoom, sqlite3 *outdb, double droprate, int buffer, const char *fname, const char *tmpdir, double gamma, int nlayers, char *prevent, char *additional, int full_detail, int low_detail, int min_detail);
long long write_tile(struct index *start, struct index *end, char *metabase, unsigned *file_bbox, int z, unsigned x, unsigned y, int detail, int basezoom, struct pool *file_keys, const char *layername, sqlite3 *outdb, double droprate, int buffer);
extern unsigned initial_x, initial_y;
extern int geometry_scale;
extern int quiet;
#define TEMP_FILES 64

1
version.h Normal file
View File

@ -0,0 +1 @@
#define VERSION "tippecanoe v1.3.0\n"