mirror of
https://github.com/mapbox/tippecanoe.git
synced 2025-06-25 01:59:11 +00:00
Compare commits
230 Commits
v1.0.2
...
multithrea
Author | SHA1 | Date | |
---|---|---|---|
02b020a2c7 | |||
4289f9ba98 | |||
f53b808dac | |||
10d85fe507 | |||
ff847f6df3 | |||
e735678e4b | |||
8214218bd7 | |||
b0fb0b5c75 | |||
a3263d521f | |||
73e77b5655 | |||
6d438b9c7b | |||
3fbd0d6546 | |||
e554a121fb | |||
eb5c78482a | |||
a38668a6da | |||
e4e14b2078 | |||
965176f254 | |||
56910fd016 | |||
3bb87227ec | |||
ef5fdf4036 | |||
3da692250d | |||
5d014e040f | |||
10bd608b9e | |||
89e1640496 | |||
3ad499fa44 | |||
f64e2c94e2 | |||
6a1895547d | |||
37ffacdeb9 | |||
592c47c549 | |||
2b25c2fe3e | |||
77b451f2c8 | |||
8c5681a582 | |||
0b47471777 | |||
329f041bf2 | |||
5dc9f50345 | |||
7154c29459 | |||
a8e2b2d55a | |||
9bd2f70516 | |||
6767aa7a5c | |||
c0480673c5 | |||
c4f517fbdc | |||
3a94106283 | |||
5ef4ea4b27 | |||
18647d18dd | |||
1a874740cf | |||
a70a8734e3 | |||
68c81162b5 | |||
efe378a075 | |||
684e995cda | |||
763bc36563 | |||
4dd28f7aae | |||
b5f374cdac | |||
5ab2673b8c | |||
601c092883 | |||
93b2687548 | |||
541d3518c2 | |||
d9974d3cc7 | |||
b3302c30e5 | |||
a57b010245 | |||
2814f1ccf2 | |||
f99d320253 | |||
ea6775f124 | |||
88b9750959 | |||
529c9aedc9 | |||
9a49534098 | |||
514da467e1 | |||
06ca52526d | |||
f131987f9e | |||
29fcbf244d | |||
2e3f03172e | |||
ed378681e4 | |||
863c9a5929 | |||
224321f6c8 | |||
caafe2fe5c | |||
86d5a542de | |||
9761010550 | |||
bc661ef3d5 | |||
b4339b2f75 | |||
a51ddbe180 | |||
10e35c4300 | |||
39cd5e210e | |||
70d11cc335 | |||
b115a07005 | |||
26beada6bd | |||
32179b7ad6 | |||
cc05f46fb7 | |||
b897712a10 | |||
a64913c989 | |||
41faf3a5c2 | |||
e01ea076ed | |||
506c801c65 | |||
9d25afa41f | |||
d55af3b3a6 | |||
2957f16b4b | |||
2bdb51e995 | |||
5d4ab6df1b | |||
3452ee92ab | |||
e5a6f981b7 | |||
9abf09eb7e | |||
138699d243 | |||
3bc5a07e5b | |||
e7b4443838 | |||
0ff6819efb | |||
6fd72d4518 | |||
c19c913bf9 | |||
65f3737325 | |||
1b72804358 | |||
18cdcb0732 | |||
af13a95dc1 | |||
b2fdcba6b0 | |||
767a581874 | |||
0d3192b863 | |||
530852ae00 | |||
4bb88e228a | |||
7724e2c329 | |||
efe3c62bb1 | |||
6951c1b72d | |||
59faead7fa | |||
a42dbd7968 | |||
62052cafab | |||
265b6866db | |||
e6c5aa9bfe | |||
e6997b00ff | |||
498e723563 | |||
dc3021656e | |||
d96dee8dad | |||
a185073f0a | |||
86a341c344 | |||
a1d3ecf9bb | |||
1a44538bdf | |||
46626e4f08 | |||
725ea71e57 | |||
cde1e60603 | |||
55e93a5d37 | |||
fbe4416fba | |||
9b34f7e6e3 | |||
d5d322f36a | |||
448617e0a7 | |||
263ae94e75 | |||
1a95504390 | |||
b70d19288e | |||
fd60cc6600 | |||
8a1f0d83e1 | |||
d9ff3f78fc | |||
34b00eca73 | |||
70291f0415 | |||
bc2f243f0b | |||
6341419229 | |||
c048311124 | |||
25072133fb | |||
142ea37e17 | |||
4001df81cc | |||
22471ab5be | |||
79a08edcf6 | |||
4eaa740f55 | |||
2a6af266b7 | |||
5ba8f2f866 | |||
95997b50c4 | |||
67fe27f70a | |||
a4c79e1ec2 | |||
ce6a1aac88 | |||
a0693446d5 | |||
38dc80ec68 | |||
901f6a76b6 | |||
555ababd2e | |||
9e162e6f8f | |||
1381f0f276 | |||
12fb2c969c | |||
167ec690a0 | |||
4f9edf7f29 | |||
fa9474ff74 | |||
d64328ac35 | |||
ed2f968b4e | |||
4041811372 | |||
85919de490 | |||
e66d976d55 | |||
1f8581c76c | |||
0cd733eb77 | |||
fd8de691eb | |||
e95cc82678 | |||
a076c5619d | |||
86925eea4c | |||
73b63133e2 | |||
2198bcc2a6 | |||
f5135ebc63 | |||
eb24c6e21e | |||
3d074653b5 | |||
a880f44a91 | |||
8002609f0c | |||
d370b07231 | |||
26bcdef06b | |||
4549d1e4f4 | |||
f3e051a610 | |||
b59a251924 | |||
f0a8e5b192 | |||
9343c5fcc1 | |||
816ef2eca8 | |||
41b28b2a1b | |||
558a7a412c | |||
572df8ad39 | |||
a8b2db8d5a | |||
97d65e6b7d | |||
b2eff13667 | |||
290e39f80c | |||
0b84f13159 | |||
5a2003cb2c | |||
32010fc893 | |||
48b5db6ae5 | |||
7f3551070e | |||
92bbf27f72 | |||
cba1b8ae7f | |||
0d0a546b1e | |||
ad17f1f282 | |||
3b9f4691c1 | |||
a40192bcde | |||
c90ba8511f | |||
34a6422c42 | |||
3f2818a814 | |||
c177b8bed2 | |||
d69431e16b | |||
105dfa73d7 | |||
a867646dfd | |||
b068635acf | |||
40ecfc0668 | |||
38a41f4df8 | |||
380550ce85 | |||
028fef470e | |||
b7b476b36c | |||
08ff40e42f | |||
eaeb55bf71 |
23
CHANGELOG.md
Normal file
23
CHANGELOG.md
Normal 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
19
MADE_WITH.md
Normal file
@ -0,0 +1,19 @@
|
||||
## [Visualizing a Month of Lightning](http://rousseau.io/2015/03/23/visualizing-a-month-of-lightning/) by Jordan Rousseau
|
||||
|
||||

|
||||
|
||||
## [Making the most detailed tweet map ever](https://www.mapbox.com/blog/twitter-map-every-tweet/) by Eric Fischer
|
||||
|
||||

|
||||
|
||||
## [Superpowering Runkeeper's 1.5 million walks, runs, and bike rides](https://www.mapbox.com/blog/runkeeper-million-routes/)
|
||||
|
||||

|
||||
|
||||
## [The Geotaggers' World Atlas](https://www.mapbox.com/blog/geotaggers-world-atlas/) by Eric Fischer
|
||||
|
||||

|
||||
|
||||
## [Atmospheric River](https://www.mapbox.com/blog/atmospheric-river/)
|
||||
|
||||

|
20
Makefile
20
Makefile
@ -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
251
README.md
@ -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
18
clip.c
@ -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
24
clipper/License.txt
Normal 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
407
clipper/README
Normal 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
4464
clipper/clipper.cpp
Normal file
File diff suppressed because it is too large
Load Diff
395
clipper/clipper.hpp
Normal file
395
clipper/clipper.hpp
Normal 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
321
decode.cc
@ -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;
|
||||
}
|
||||
|
14
enumerate.c
14
enumerate.c
@ -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) {
|
||||
|
430
geometry.cc
430
geometry.cc
@ -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;
|
||||
}
|
||||
|
12
geometry.hh
12
geometry.hh
@ -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);
|
||||
|
65
jsonpull.c
65
jsonpull.c
@ -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;
|
||||
}
|
||||
|
19
jsonpull.h
19
jsonpull.h
@ -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
338
man/tippecanoe.1
Normal 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
|
71
mbtiles.c
71
mbtiles.c
@ -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) {
|
||||
|
@ -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
69
memfile.c
Normal 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
10
memfile.h
Normal 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
64
pool.c
@ -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
9
pool.h
@ -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);
|
||||
|
@ -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
547
tile-join.cc
Normal 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;
|
||||
}
|
24
tile.h
24
tile.h
@ -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
|
||||
|
Reference in New Issue
Block a user