mirror of
https://github.com/mapbox/tippecanoe.git
synced 2025-06-24 17:55:11 +00:00
Compare commits
686 Commits
Author | SHA1 | Date | |
---|---|---|---|
2daf84a6c1 | |||
5d45f89d7e | |||
18e53cd7fb | |||
158b5fb292 | |||
4bb73aa6bc | |||
5808c95111 | |||
1fa797bc17 | |||
62554b6cd9 | |||
9ece7c0d36 | |||
97ace997ff | |||
80eeacc98e | |||
62b9d12a09 | |||
ea52e88a58 | |||
9f8c8508ff | |||
c678f784b7 | |||
e0c7f1b91a | |||
6d2713b02a | |||
efa40d20ab | |||
ddb79937d9 | |||
ed9f844cef | |||
065cc1d78d | |||
25bf3957d1 | |||
ec00ac2516 | |||
28e99f8e72 | |||
ea53f5edcf | |||
9e81d5a3b2 | |||
33b055bf93 | |||
fc810d4156 | |||
d96b521570 | |||
78853baf87 | |||
df9fa602bd | |||
7bba6bc7e1 | |||
9e3fed8834 | |||
fc335e2221 | |||
b550c7b4a6 | |||
dcdfb94ba9 | |||
42ee195bc3 | |||
9dc92a452a | |||
1b14034cb5 | |||
363d21bfc1 | |||
4d3e30773f | |||
b147ff22b1 | |||
7be3710b40 | |||
fcab1da952 | |||
a8a183df2d | |||
9d06af968a | |||
ffab876002 | |||
99dc019d92 | |||
e89bdecb6d | |||
18b6473cdb | |||
382563a5b1 | |||
26a07b2167 | |||
f95b9bcc65 | |||
4f7459c797 | |||
e051c53051 | |||
ac12932089 | |||
7419d64aab | |||
25b9c51ac8 | |||
0704ec9882 | |||
945d80400b | |||
1bc475b789 | |||
907ddeb7ba | |||
90afdf8fb2 | |||
8df0455230 | |||
454f75ef91 | |||
435a788c3f | |||
9387fe6546 | |||
b1ae63ab86 | |||
451175dd35 | |||
8c2df5f7b4 | |||
87bdaa1a25 | |||
185f7d752e | |||
e805d113d0 | |||
610afc2332 | |||
6da2090960 | |||
454ae96242 | |||
2eb7f5dfae | |||
b746688018 | |||
5421dbf8f9 | |||
0505e0e7d5 | |||
255f795c91 | |||
32b14b1269 | |||
b31b0b04b6 | |||
28f773a933 | |||
45fbdf43c5 | |||
6376c283ba | |||
5f1c0e151f | |||
ca4aad0c64 | |||
4b20948369 | |||
3eea8b0e4b | |||
9c708aa3c4 | |||
cf28e6b88b | |||
506526ddfa | |||
4fec7a162e | |||
5229990523 | |||
739445cb6c | |||
f9c9ff3853 | |||
949468e6f5 | |||
a3150642d3 | |||
752f77d58e | |||
cc7c0cc6c9 | |||
bd2423a5ea | |||
53de8a2014 | |||
b8ffdda32e | |||
9c79cf7162 | |||
d3ae48534e | |||
1cf3b0accc | |||
5dfbc4a281 | |||
0acdeef525 | |||
e18748e29b | |||
1b75ca33b4 | |||
b7fb576544 | |||
b2999fe9a8 | |||
03199a77a5 | |||
7aebce7e0e | |||
9949d7ed1a | |||
2530efd525 | |||
216c0cf428 | |||
bc84b41246 | |||
5483720ee1 | |||
5d6e6e07d6 | |||
15df8e8431 | |||
addbd0a854 | |||
de40169f16 | |||
0a3e4ad561 | |||
1ef2005f04 | |||
f0517d090b | |||
19c132e79c | |||
98cf4d94aa | |||
ba8966a4ae | |||
79363e397b | |||
2e80377b24 | |||
8d67e460d4 | |||
5c881c11ef | |||
baab0cef55 | |||
60629da37a | |||
7221c7ba0d | |||
c9cc12e7ff | |||
4958c47abc | |||
18e8bea18d | |||
510df9e28f | |||
bec1f41a4f | |||
3fb833979e | |||
c4cd7faaaa | |||
e9eca57393 | |||
796fa19729 | |||
28ebec275a | |||
759bceb986 | |||
faa952ec7d | |||
6f295f29fc | |||
50b1eadf87 | |||
856c055857 | |||
206c75f0ab | |||
87a1bb7851 | |||
3cb09d0e2a | |||
6683939483 | |||
07ab900393 | |||
d1a0e495ce | |||
d64ac19f11 | |||
c2bb35e6ff | |||
262fbf26f0 | |||
9dac499ab4 | |||
8d6bd4e10f | |||
54983c7536 | |||
4d8a3c6bdc | |||
bc3d4c6a34 | |||
7f90ba5e1b | |||
4da6b932b3 | |||
b0229530ac | |||
11c6c869aa | |||
9f1913bc37 | |||
760a0c9209 | |||
c9eed0d457 | |||
2318673108 | |||
6e231f5911 | |||
c650329eb2 | |||
48486b5d7b | |||
38bca4a179 | |||
48f7f1dd38 | |||
bb8b01a702 | |||
54532795f6 | |||
32b3a15406 | |||
53731709b2 | |||
7128ac44d5 | |||
b65a5d1bb8 | |||
02cb3a3fb8 | |||
5494b7da3d | |||
dc3aa2ddfa | |||
a4825f6af4 | |||
3f7ba2915a | |||
445c33fb88 | |||
6db02e8457 | |||
73398a0484 | |||
0244f04b71 | |||
d0d953f4a2 | |||
09565b4632 | |||
974bfb3543 | |||
450a8a741f | |||
dbd69142b0 | |||
14b618adc9 | |||
9f7dc4b46d | |||
72b8142388 | |||
33b0d5c15c | |||
8e8b74b0ce | |||
f070c7464b | |||
59dd095607 | |||
2cf0524037 | |||
8ba884530a | |||
43312fd83e | |||
b8cfab1eaf | |||
af69c85d23 | |||
a383f5c725 | |||
066934a666 | |||
cb4630452b | |||
7006272b75 | |||
767763c855 | |||
cc6a97f112 | |||
0d6112cf20 | |||
970af8109e | |||
1b26becad9 | |||
d01d8177a0 | |||
6b29966e4a | |||
7f843c4d36 | |||
0304d164d8 | |||
d5b0da3ae0 | |||
a45fec573d | |||
3cea1257a5 | |||
8ff6e73b6d | |||
cc2cae144e | |||
239a798bfe | |||
db2c7dbe62 | |||
a1d002ccb1 | |||
e21e4d3b88 | |||
3d1ebdb82f | |||
3c827b1d68 | |||
ebb0334ef0 | |||
08fc2f281e | |||
1c4aa57ffd | |||
623e0c90fe | |||
ddf4a45b33 | |||
605ccbe077 | |||
35aa291aac | |||
71df4d3e81 | |||
e690a1b585 | |||
fac0ebbf52 | |||
f1eede1106 | |||
834c05038c | |||
ca91cec923 | |||
2b936af96a | |||
18e345efb0 | |||
61cbc3eca0 | |||
10a1bc8580 | |||
a2dfaf85d0 | |||
bfb6778e07 | |||
d66d05213f | |||
f87b71b739 | |||
ebe10a27d7 | |||
c604a51039 | |||
e939a85251 | |||
45b1a1d9e0 | |||
06ed9d14d8 | |||
e4e39289a2 | |||
a8a342f701 | |||
7fe0c87806 | |||
27154c8f06 | |||
30a277a43e | |||
79df57f2c3 | |||
3235955807 | |||
bc0edec753 | |||
b73e710855 | |||
03577cf967 | |||
2d625d5807 | |||
2b7d64736f | |||
17942b3a89 | |||
7ab94d43c6 | |||
8726366a4c | |||
2e5b597ca1 | |||
8fa5a5ff87 | |||
8e7b22cf1b | |||
e2a34929d1 | |||
042fa77357 | |||
21b9b1993f | |||
d49606babf | |||
e07a4dd51b | |||
eb966ae398 | |||
aaf08a6c55 | |||
fc32a0e897 | |||
01884a3bf6 | |||
6524b6510e | |||
b03dabe098 | |||
5687ee041f | |||
be79d13cbc | |||
2e32004589 | |||
82a2b5dfdc | |||
ed0b69a3c8 | |||
ec475b3beb | |||
afb5cece96 | |||
f3e6afa4e9 | |||
f2f76082df | |||
e4743d9527 | |||
d7f44ab082 | |||
1acd771743 | |||
ad86d06cce | |||
1b1be9f872 | |||
ba1c343204 | |||
ca028dd1cc | |||
8ac7c46788 | |||
2234e37883 | |||
8230987405 | |||
9964784e77 | |||
ac67013930 | |||
9a12a76855 | |||
6fd4918c4d | |||
ecbfad2576 | |||
ab86846d4b | |||
4754084130 | |||
3f54a70459 | |||
2b1cba0b53 | |||
f318fcc657 | |||
621e0afc3c | |||
b6163dd435 | |||
76d86f7d45 | |||
4dec9b43ae | |||
2d87059a04 | |||
f1c3811d97 | |||
069807caf4 | |||
dca1ebbbfb | |||
23004808e4 | |||
450032a62e | |||
948680fbeb | |||
fda0e1f28a | |||
aa7191b1ee | |||
87ce5b9310 | |||
0152db4a20 | |||
30aa422483 | |||
4f974b3dc6 | |||
ba62ab8596 | |||
891f99f24c | |||
1b60fdbaf0 | |||
20e6e80cb1 | |||
c18f9a1572 | |||
50910b211b | |||
d89d46da65 | |||
95cc1672b9 | |||
5a68886b4d | |||
12a1258797 | |||
0585742a20 | |||
0801a9324b | |||
8ae9a8b665 | |||
e39db074fe | |||
1960eb8dae | |||
6467a5b70d | |||
21042a7308 | |||
c217a77b0a | |||
621cf72e5a | |||
cc734c8709 | |||
41c026796d | |||
d551231f6c | |||
dc42c7ccc2 | |||
faf40658a6 | |||
93a325605c | |||
e2b9b96ba8 | |||
a3e95db0c3 | |||
dfbb13e7db | |||
d13e08c9b5 | |||
4c7de922ce | |||
197d36bdc3 | |||
68e4f312f8 | |||
18a5300e87 | |||
cd47a398fa | |||
ee6da93494 | |||
9ebeb47d24 | |||
894a0acc74 | |||
2f1945cfa6 | |||
68a55b8749 | |||
86a4ce67a6 | |||
face450102 | |||
19117d8060 | |||
54e9cd3cae | |||
1f9768ce24 | |||
ebb26ee14c | |||
d9c22135e5 | |||
42a56b1ad3 | |||
91e469aac2 | |||
748ef3b1d5 | |||
ecadd779c9 | |||
8ae1ec5379 | |||
be922702ce | |||
4c2d80ea17 | |||
e000bcc261 | |||
a2d12f178f | |||
84a6aa6d73 | |||
c3d23675d1 | |||
c77c2a2b1e | |||
93920d06e1 | |||
764a8ac17c | |||
0f8b32c69f | |||
2518f238d4 | |||
7ce882c035 | |||
c9e4275217 | |||
bdd95af8b2 | |||
d349b7700d | |||
7b71f41e43 | |||
86ff221663 | |||
681907e88d | |||
8d502eb805 | |||
c232e565db | |||
0fd4454129 | |||
dbb789dadc | |||
dd07511193 | |||
9a21c04f06 | |||
223d837736 | |||
12d744e961 | |||
6384b6a49a | |||
a5b1378d1a | |||
076dfcdfeb | |||
f65faaf3da | |||
5c5fbe2617 | |||
5665d08745 | |||
607ea6c643 | |||
15a48e67d6 | |||
caac717b17 | |||
8b6a51e20b | |||
4d6ebaa088 | |||
c65a388597 | |||
12784de211 | |||
228567364f | |||
cef6b022dd | |||
0d56d1bf38 | |||
f9a007e8c3 | |||
3840176d5c | |||
5f09ccae88 | |||
18b2a2c39c | |||
4b66aa828d | |||
1581b79a3e | |||
754cbdc634 | |||
4e1611eec9 | |||
8d7f8af1d9 | |||
b7df68c164 | |||
5878213516 | |||
7be21f6046 | |||
7c031a9796 | |||
5943c82457 | |||
8d09f0769e | |||
ad89fb88fb | |||
b98bf6e8c7 | |||
75be013059 | |||
e7ee83f27b | |||
f4818ffb07 | |||
34b1b215f4 | |||
ed8fbd0236 | |||
b114e22d39 | |||
6caf20b9c8 | |||
6cea2d5db6 | |||
235dbf57af | |||
f0b32cf710 | |||
e7f264fa51 | |||
0b3e731f0b | |||
071b4efdab | |||
4d1ddc5a03 | |||
e6fc22187a | |||
8f02aa40c4 | |||
205c28bb64 | |||
5fc261020c | |||
372194cee9 | |||
b1620f6c59 | |||
23a4ed8754 | |||
f5111857d2 | |||
e453e32321 | |||
915b1481ad | |||
200f6777ba | |||
635429cd87 | |||
eebc8f7639 | |||
c79f19e3ca | |||
fa0e38da2b | |||
e982b2f6a7 | |||
8280c3f99d | |||
cc28bbab3a | |||
5a09fcc35e | |||
a373c2516a | |||
9ec52f2c90 | |||
7e6d10c2fa | |||
62f135a97d | |||
00aed42c0e | |||
518c8db790 | |||
54cbc570f1 | |||
855e344552 | |||
97d0b2a1b5 | |||
3cafef89f1 | |||
7b03e1ee87 | |||
9078098511 | |||
b3078ddeec | |||
81e3f09024 | |||
9304bc5ca1 | |||
ba82ef8274 | |||
3eb1237b0a | |||
6ac9ca3709 | |||
18f9a8dbb7 | |||
71f57793fd | |||
77bf14bfb0 | |||
b1771a3365 | |||
7445feb845 | |||
62ee53992b | |||
65c095cc2b | |||
6a505cdba7 | |||
24a182772f | |||
c164a3e69b | |||
04d0cc6fa1 | |||
412be8e6fc | |||
3ba45c7277 | |||
bd845ac57d | |||
dd0a135b01 | |||
240ccbd219 | |||
dba24959ba | |||
cf3a0800b8 | |||
b80a1d7621 | |||
c7d146ea48 | |||
8776f17980 | |||
977effc96d | |||
ab8ecb7e00 | |||
331707f88a | |||
a5db055c50 | |||
e8e949fac9 | |||
52ceaaddb6 | |||
ef8af63ab4 | |||
9c3fb0f669 | |||
5db7b504e8 | |||
eb8ab1dd58 | |||
d0980e29d0 | |||
1bb31882fa | |||
4016876670 | |||
b301512860 | |||
27dadc8ade | |||
2f7b5d8afd | |||
733092abf6 | |||
9632c14551 | |||
7fe3de9cfa | |||
fb6551c59e | |||
30d54ff50d | |||
685e1a4360 | |||
922bef72c8 | |||
9eb3a7f7ec | |||
eab593fea5 | |||
f87a9d1660 | |||
92deeb58f9 | |||
6ec453229c | |||
24bde0be03 | |||
263a1b9e5f | |||
f9f57ebb3f | |||
ad0eba7ccd | |||
acdb5b72db | |||
a0234923b7 | |||
1669d96510 | |||
63e0c89c4b | |||
dc01d33402 | |||
0ecc6fa1c5 | |||
5a8bfa23a4 | |||
6173180cee | |||
569fd97475 | |||
6c7a52147b | |||
d2a8761483 | |||
d19680e392 | |||
7dc586bc84 | |||
8b5168cb2b | |||
178b5d0054 | |||
65ee2cf0a5 | |||
242a657fc1 | |||
510247c2fb | |||
e75bb4a16a | |||
198a5a06d4 | |||
a0831cd71e | |||
f4677d5de1 | |||
a394c9e93d | |||
79b73e6317 | |||
ab300c2080 | |||
5ec6ce1743 | |||
63ea7bdc4e | |||
bb365bde44 | |||
7f918bd79b | |||
a7be83381a | |||
94a15eaf85 | |||
da92b93b62 | |||
f558b78380 | |||
1c1ae6a8be | |||
b66ab10252 | |||
682a272f91 | |||
a320248857 | |||
9fb1208b79 | |||
29fa3d8541 | |||
f03b89656c | |||
aee12ac1fe | |||
ae4f03d92b | |||
7d47226444 | |||
834ba19277 | |||
8777bdf5d3 | |||
1362ac448f | |||
fa56adc530 | |||
eaff7b93c5 | |||
8c6f6250b1 | |||
ea77e3db6f | |||
bbf9716361 | |||
9c53268a2b | |||
860189a577 | |||
c46f4cfc75 | |||
9cbbc4c68d | |||
cef4d2cd47 | |||
3a1f074c96 | |||
835fafe30a | |||
30a987d9ba | |||
218a2fc75e | |||
638cfef2b6 | |||
5bdbb8a911 | |||
55e7e20a73 | |||
9c1f2b2123 | |||
1b68dcdc22 | |||
1cc65d61ba | |||
62e1018c87 | |||
4517bd6e90 | |||
a1fe858151 | |||
31f254ac99 | |||
5c2106c0dc | |||
28b8f1c326 | |||
c74a9a8325 | |||
47cb2434e6 | |||
c67b5f33bd | |||
5a8f9f11c0 | |||
b3847c1da1 | |||
801c5d6574 | |||
7b5069f2f6 | |||
150e3663e1 | |||
aa75f4a4c4 | |||
a00903ecc6 | |||
72086b7e92 | |||
2fdec7d2e4 | |||
3549aa35e8 | |||
859202165d | |||
ece768bfe4 | |||
c1ec437756 | |||
bdac4d4e80 | |||
bf619a315c | |||
e53c8ed7e6 | |||
c87d6eadfc | |||
8e17c3aa09 | |||
cf6a2d3a67 | |||
71ac6596af | |||
6a5461763c | |||
a2060299c9 | |||
854dc2bca5 | |||
8ab5bb4809 | |||
6a2e80769e | |||
4fc671f1d1 | |||
c2fa8e3633 | |||
ad4060eced | |||
9c0e2cdfa7 | |||
5e7f718afc | |||
57ff54e683 | |||
5dc773ffae | |||
0e5b513637 | |||
a338f5390f | |||
c8a8915064 | |||
9f10f48bfb | |||
5194a39c16 | |||
daf1941ba9 | |||
569825324a | |||
16df86c26e | |||
d940eb1cef | |||
d1dc310bbc | |||
e3823c966c | |||
5960a15fcd | |||
6530e155eb | |||
8cf81483b1 | |||
3f14a0dd55 | |||
5554b9cbba | |||
a114a890d8 | |||
58e268777c | |||
3d1ceac96a | |||
87d86ecfc9 | |||
7514797c4c | |||
d8fe69a99e | |||
679189e5a2 | |||
c82e3e98c3 | |||
4256473283 | |||
112f451c66 | |||
72478ae13e | |||
94bebbd276 | |||
adfceed554 |
5
.dockerignore
Normal file
5
.dockerignore
Normal file
@ -0,0 +1,5 @@
|
||||
# Don't copy Dockerfile or git items
|
||||
.gitignore
|
||||
.git
|
||||
Dockerfile
|
||||
Dockerfile.centos7
|
23
.gitignore
vendored
23
.gitignore
vendored
@ -4,6 +4,9 @@
|
||||
*.o
|
||||
*.obj
|
||||
|
||||
# Autogenerated dependencies
|
||||
*.d
|
||||
|
||||
# Precompiled Headers
|
||||
*.gch
|
||||
*.pch
|
||||
@ -27,3 +30,23 @@
|
||||
*.exe
|
||||
*.out
|
||||
*.app
|
||||
tippecanoe
|
||||
tile-join
|
||||
tippecanoe-decode
|
||||
tippecanoe-enumerate
|
||||
tippecanoe-json-tool
|
||||
unit
|
||||
|
||||
# Tests
|
||||
tests/**/*.mbtiles
|
||||
tests/**/*.check
|
||||
tests/**/*.geobuf
|
||||
|
||||
# Vim
|
||||
*.swp
|
||||
|
||||
# Mac
|
||||
.DS_Store
|
||||
|
||||
# Nodejs
|
||||
node_modules
|
||||
|
48
.travis.yml
48
.travis.yml
@ -1,9 +1,37 @@
|
||||
language: generic
|
||||
language: node_js
|
||||
node_js:
|
||||
- "6"
|
||||
|
||||
sudo: false
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# test on docker+centos7
|
||||
- os: linux
|
||||
compiler: clang
|
||||
services:
|
||||
- docker
|
||||
sudo: true
|
||||
dist: trusty
|
||||
env: DOCKERFILE=Dockerfile.centos7
|
||||
before_install: []
|
||||
install:
|
||||
- docker build -t tippecanoe-image -f ${DOCKERFILE} .
|
||||
script:
|
||||
- docker run -it tippecanoe-image
|
||||
# test on docker+ubuntu
|
||||
- os: linux
|
||||
compiler: clang
|
||||
services:
|
||||
- docker
|
||||
sudo: true
|
||||
dist: trusty
|
||||
env: DOCKERFILE=Dockerfile
|
||||
before_install: []
|
||||
install:
|
||||
- docker build -t tippecanoe-image -f ${DOCKERFILE} .
|
||||
script:
|
||||
- docker run -it tippecanoe-image
|
||||
# debug+integer-santizer build
|
||||
- os: linux
|
||||
compiler: clang
|
||||
@ -15,7 +43,7 @@ matrix:
|
||||
# debug+leak+address-sanitizer build
|
||||
- os: linux
|
||||
compiler: clang
|
||||
env: CLANG_VERSION='3.8.0' BUILDTYPE=Debug ASAN_OPTIONS=detect_leaks=1 CC="clang-3.8" CXX="clang++-3.8" CXXFLAGS="-fsanitize=address" CFLAGS="-fsanitize=address" LDFLAGS="-fsanitize=address"
|
||||
env: CLANG_VERSION='3.8.0' BUILDTYPE=Debug ASAN_OPTIONS=detect_leaks=1 CC="clang-3.8" CXX="clang++-3.8" CXXFLAGS="-fsanitize=address,undefined" CFLAGS="-fsanitize=address,undefined" LDFLAGS="-fsanitize=address,undefined" FEWER=true
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test' ]
|
||||
@ -24,6 +52,13 @@ matrix:
|
||||
- os: linux
|
||||
compiler: clang
|
||||
env: CLANG_VERSION='3.8.0' BUILDTYPE=Debug CC="clang-3.8" CXX="clang++-3.8" CXXFLAGS="--coverage" CFLAGS="--coverage" LDFLAGS="--coverage"
|
||||
after_script:
|
||||
- mason install llvm-cov 3.9.1
|
||||
- mason link llvm-cov 3.9.1
|
||||
- which llvm-cov
|
||||
- curl -S -f https://codecov.io/bash -o codecov
|
||||
- chmod +x codecov
|
||||
- ./codecov -x "llvm-cov gcov" -Z
|
||||
addons:
|
||||
apt:
|
||||
sources: ['ubuntu-toolchain-r-test' ]
|
||||
@ -68,9 +103,8 @@ install:
|
||||
- BUILDTYPE=${BUILDTYPE} make -j
|
||||
|
||||
script:
|
||||
- BUILDTYPE=${BUILDTYPE} make test
|
||||
- if [ -n "${COVERAGE}" ]; then
|
||||
/usr/bin/llvm-cov-3.5 -lp *.o;
|
||||
pip install --user cpp-coveralls;
|
||||
~/.local/bin/coveralls --no-gcov -i ./ --exclude clipper;
|
||||
- npm install geobuf
|
||||
- if [ -n "${FEWER}" ]; then
|
||||
BUILDTYPE=${BUILDTYPE} make fewer-tests; else
|
||||
BUILDTYPE=${BUILDTYPE} make test geobuf-test;
|
||||
fi
|
||||
|
497
CHANGELOG.md
497
CHANGELOG.md
@ -1,3 +1,500 @@
|
||||
## 1.36.0
|
||||
|
||||
* Update Wagyu to version 0.5.0
|
||||
|
||||
## 1.35.0
|
||||
|
||||
* Fix calculation of mean when accumulating attributes in clusters
|
||||
|
||||
## 1.34.6
|
||||
|
||||
* Fix crash when there are null entries in the metadata table
|
||||
|
||||
## 1.34.5
|
||||
|
||||
* Fix line numbers in GeoJSON feature parsing error messages
|
||||
|
||||
## 1.34.4
|
||||
|
||||
* Be careful to avoid undefined behavior from shifting negative numbers
|
||||
|
||||
## 1.34.3
|
||||
|
||||
* Add an option to keep intersection nodes from being simplified away
|
||||
|
||||
## 1.34.2
|
||||
|
||||
* Be more consistent about when longitudes beyond 180 are allowed.
|
||||
Now if the entire feature is beyond 180, it will still appear.
|
||||
|
||||
## 1.34.1
|
||||
|
||||
* Don't run shell filters if the current zoom is below the minzoom
|
||||
* Fix -Z and -z for tile directories in tile-join and tippecanoe-decode
|
||||
* Return a successful error status for --help and --version
|
||||
|
||||
## 1.34.0
|
||||
|
||||
* Record the command line options in the tileset metadata
|
||||
|
||||
## 1.33.0
|
||||
|
||||
* MultiLineStrings were previously ignored in Geobuf input
|
||||
|
||||
## 1.32.12
|
||||
|
||||
* Accept .mvt as well as .pbf in directories of tiles
|
||||
* Allow tippecanoe-decode and tile-join of directories with no metadata
|
||||
|
||||
## 1.32.11
|
||||
* Don't let attribute exclusion apply to the attribute that has been specified
|
||||
to become the feature ID
|
||||
|
||||
## 1.32.10
|
||||
|
||||
* Fix a bug that disallowed a per-feature minzoom of 0
|
||||
|
||||
## 1.32.9
|
||||
|
||||
* Limit tile detail to 30 and buffer size to 127 to prevent coordinate
|
||||
delta overflow in vector tiles.
|
||||
|
||||
## 1.32.8
|
||||
|
||||
* Better error message if the output tileset already exists
|
||||
|
||||
## 1.32.7
|
||||
|
||||
* Point features may now be coalesced into MultiPoint features with --coalesce.
|
||||
* Add --hilbert option to put features in Hilbert Curve sequence
|
||||
|
||||
## 1.32.6
|
||||
|
||||
* Make it an error, not a warning, to have missing coordinates for a point
|
||||
|
||||
## 1.32.5
|
||||
|
||||
* Use less memory on lines and polygons that are too small for the tile
|
||||
* Fix coordinate rounding problem that was causing --grid-low-zooms grids
|
||||
to be lost at low zooms if the original polygons were not aligned to
|
||||
tile boundaries
|
||||
|
||||
## 1.32.4
|
||||
|
||||
* Ignore leading zeroes when converting string attributes to feature IDs
|
||||
|
||||
## 1.32.3
|
||||
|
||||
* Add an option to convert stringified number feature IDs to numbers
|
||||
* Add an option to use a specified feature attribute as the feature ID
|
||||
|
||||
## 1.32.2
|
||||
|
||||
* Warn in tile-join if tilesets being joined have inconsistent maxzooms
|
||||
|
||||
## 1.32.1
|
||||
|
||||
* Fix null pointer crash when reading filter output that does not
|
||||
tag features with their extent
|
||||
* Add `--clip-bounding-box` option to clip input geometry
|
||||
|
||||
## 1.32.0
|
||||
|
||||
* Fix a bug that allowed coalescing of features with mismatched attributes
|
||||
if they had been passed through a shell prefilter
|
||||
|
||||
## 1.31.7
|
||||
|
||||
* Create the output tile directory even if there are no valid features
|
||||
|
||||
## 1.31.6
|
||||
|
||||
* Issue an error message in tile-join if minzoom is greater than maxzoom
|
||||
|
||||
## 1.31.5
|
||||
|
||||
* Add options to change the tilestats limits
|
||||
|
||||
## 1.31.4
|
||||
|
||||
* Keep tile-join from generating a tileset name longer than 255 characters
|
||||
|
||||
## 1.31.3
|
||||
|
||||
* Fix the missing filename in JSON parsing warning messages
|
||||
|
||||
## 1.31.2
|
||||
|
||||
* Don't accept anything inside another JSON object's properties as a
|
||||
feature or geometry of its own.
|
||||
|
||||
## 1.31.1
|
||||
|
||||
* Add --exclude-all to tile-join
|
||||
|
||||
## 1.31.0
|
||||
|
||||
* Upgrade Wagyu to version 0.4.3
|
||||
|
||||
## 1.30.6
|
||||
|
||||
* Take cluster distance into account when guessing a maxzoom
|
||||
|
||||
## 1.30.4
|
||||
|
||||
* Features within the z0 tile buffer of the antimeridian (not only
|
||||
those that cross it) are duplicated on both sides.
|
||||
|
||||
## 1.30.3
|
||||
|
||||
* Add an option to automatically assign ids to features
|
||||
|
||||
## 1.30.2
|
||||
|
||||
* Don't guess a higher maxzoom than is allowed for manual selection
|
||||
|
||||
## 1.30.1
|
||||
|
||||
* Ensure that per-feature minzoom and maxzoom are integers
|
||||
* Report compression errors in tippecanoe-decode
|
||||
* Add the ability to specify the file format with -L{"format":"…"}
|
||||
* Add an option to treat empty CSV columns as nulls, not empty strings
|
||||
|
||||
## 1.30.0
|
||||
|
||||
* Add a filter extension to allow filtering individual attributes
|
||||
|
||||
## 1.29.3
|
||||
|
||||
* Include a generator field in tileset metadata with the Tippecanoe version
|
||||
|
||||
## 1.29.2
|
||||
|
||||
* Be careful to remove null attributes from prefilter/postfilter output
|
||||
|
||||
## 1.29.1
|
||||
|
||||
* Add --use-source-polygon-winding and --reverse-source-polygon-winding
|
||||
|
||||
## 1.29.0
|
||||
|
||||
* Add the option to specify layer file, name, and description as JSON
|
||||
* Add the option to specify the description for attributes in the
|
||||
tileset metadata
|
||||
* In CSV input, a trailing comma now counts as a trailing empty field
|
||||
* In tippecanoe-json-tool, an empty CSV field is now an empty string,
|
||||
not null (for consistency with tile-join)
|
||||
|
||||
## 1.28.1
|
||||
|
||||
* Explicitly check for infinite and not-a-number input coordinates
|
||||
|
||||
## 1.28.0
|
||||
|
||||
* Directly support gzipped GeoJSON as input files
|
||||
|
||||
## 1.27.16
|
||||
|
||||
* Fix thread safety issues related to the out-of-disk-space checker
|
||||
|
||||
## 1.27.15
|
||||
|
||||
* --extend-zooms-if-still-dropping now also extends zooms if features
|
||||
are dropped by --force-feature-limit
|
||||
|
||||
## 1.27.14
|
||||
|
||||
* Use an exit status of 100 if some zoom levels were successfully
|
||||
written but not all zoom levels could be tiled.
|
||||
|
||||
## 1.27.13
|
||||
|
||||
* Allow filtering features by zoom level in conditional expressions
|
||||
* Lines in CSV input with empty geometry columns will be ignored
|
||||
|
||||
## 1.27.12
|
||||
|
||||
* Check integrity of sqlite3 file before decoding or tile-joining
|
||||
|
||||
## 1.27.11
|
||||
|
||||
* Always include tile and layer in tippecanoe-decode, fixing corrupt JSON.
|
||||
* Clean up writing of JSON in general.
|
||||
|
||||
## 1.27.10
|
||||
|
||||
* Add --progress-interval setting to reduce progress indicator frequency
|
||||
|
||||
## 1.27.9
|
||||
|
||||
* Make clusters look better by averaging locations of clustered points
|
||||
|
||||
## 1.27.8
|
||||
|
||||
* Add --accumulate-attribute to keep attributes of dropped, coalesced,
|
||||
or clustered features
|
||||
* Make sure numeric command line arguments are actually numbers
|
||||
* Don't coalesce features whose non-string-pool attributes don't match
|
||||
|
||||
## 1.27.7
|
||||
|
||||
* Add an option to produce only a single tile
|
||||
* Retain non-ASCII characters in layernames generated from filenames
|
||||
* Remember to close input files after reading them
|
||||
* Add --coalesce-fraction-as-needed and --coalesce-densest-as-needed
|
||||
* Report distances in both feet and meters
|
||||
|
||||
## 1.27.6
|
||||
|
||||
* Fix opportunities for integer overflow and out-of-bounds references
|
||||
|
||||
## 1.27.5
|
||||
|
||||
* Add --cluster-densest-as-needed to cluster features
|
||||
* Add --maximum-tile-features to set the maximum number of features in a tile
|
||||
|
||||
## 1.27.4
|
||||
|
||||
* Support CSV point input
|
||||
* Don't coalesce features that have different IDs but are otherwise identical
|
||||
* Remove the 700-point limit on coalesced features, since polygon merging
|
||||
is no longer a performance problem
|
||||
|
||||
## 1.27.3
|
||||
|
||||
* Clean up duplicated code for reading tiles from a directory
|
||||
|
||||
## 1.27.2
|
||||
|
||||
* Tippecanoe-decode can decode directories of tiles, not just mbtiles
|
||||
* The --allow-existing option works on directories of tiles
|
||||
* Trim .geojson, not just .json, when making layer names from filenames
|
||||
|
||||
## 1.27.1
|
||||
|
||||
* Fix a potential null pointer when parsing GeoJSON with bare geometries
|
||||
* Fix a bug that could cause the wrong features to be coalesced when
|
||||
input was parsed in parallel
|
||||
|
||||
## 1.27.0
|
||||
|
||||
* Add tippecanoe-json-tool for sorting and joining GeoJSON files
|
||||
* Fix problem where --detect-shared-borders could simplify polygons away
|
||||
* Attach --coalesce-smallest-as-needed leftovers to the last feature, not the first
|
||||
* Fix overflow when iterating through 0-length lists backwards
|
||||
|
||||
## 1.26.7
|
||||
|
||||
* Add an option to quiet the progress indicator but not warnings
|
||||
* Enable more compiler warnings and fix related problems
|
||||
|
||||
## 1.26.6
|
||||
|
||||
* Be more careful about checking for overflow when parsing numbers
|
||||
|
||||
## 1.26.5
|
||||
|
||||
* Support UTF-16 surrogate pairs in JSON strings
|
||||
* Support arbitrarily long lines in CSV files.
|
||||
* Treat CSV fields as numbers only if they follow JSON number syntax
|
||||
|
||||
## 1.26.4
|
||||
|
||||
* Array bounds bug fix in binary to decimal conversion library
|
||||
|
||||
## 1.26.3
|
||||
|
||||
* Guard against impossible coordinates when decoding tilesets
|
||||
|
||||
## 1.26.2
|
||||
|
||||
* Make sure to encode tile-joined integers as ints, not doubles
|
||||
|
||||
## 1.26.1
|
||||
|
||||
* Add tile-join option to rename layers
|
||||
|
||||
## 1.26.0
|
||||
|
||||
Fix error when parsing attributes with empty-string keys
|
||||
|
||||
## 1.25.0
|
||||
|
||||
* Add --coalesce-smallest-as-needed strategy for reducing tile sizes
|
||||
* Add --stats option to tipppecanoe-decode
|
||||
|
||||
## 1.24.1
|
||||
|
||||
* Limit the size and depth of the string pool for better performance
|
||||
|
||||
## 1.24.0
|
||||
|
||||
* Add feature filters using the Mapbox GL Style Specification filter syntax
|
||||
|
||||
## 1.23.0
|
||||
|
||||
* Add input support for Geobuf file format
|
||||
|
||||
## 1.22.2
|
||||
|
||||
* Add better diagnostics for NaN or Infinity in input JSON
|
||||
|
||||
## 1.22.1
|
||||
|
||||
* Fix tilestats generation when long string attribute values are elided
|
||||
* Add option not to produce tilestats
|
||||
* Add tile-join options to select zoom levels to copy
|
||||
|
||||
## 1.22.0
|
||||
|
||||
* Add options to filter each tile's contents through a shell pipeline
|
||||
|
||||
## 1.21.0
|
||||
|
||||
* Generate layer, feature, and attribute statistics as part of tileset metadata
|
||||
|
||||
## 1.20.1
|
||||
|
||||
* Close mbtiles file properly when there are no valid features in the input
|
||||
|
||||
## 1.20.0
|
||||
|
||||
* Add long options to tippecanoe-decode and tile-join. Add --quiet to tile-join.
|
||||
|
||||
## 1.19.3
|
||||
|
||||
* Upgrade protozero to version 1.5.2
|
||||
|
||||
## 1.19.2
|
||||
|
||||
* Ignore UTF-8 byte order mark if present
|
||||
|
||||
## 1.19.1
|
||||
|
||||
* Add an option to increase maxzoom if features are still being dropped
|
||||
|
||||
## 1.19.0
|
||||
|
||||
* Tile-join can merge and create directories, not only mbtiles
|
||||
* Maxzoom guessing (-zg) takes into account resolution within each feature
|
||||
|
||||
## 1.18.2
|
||||
|
||||
* Fix crash with very long (>128K) attribute values
|
||||
|
||||
## 1.18.1
|
||||
|
||||
* Only warn once about invalid polygons in tippecanoe-decode
|
||||
|
||||
## 1.18.0
|
||||
|
||||
* Fix compression of tiles in tile-join
|
||||
* Calculate the tileset bounding box in tile-join from the tile boundaries
|
||||
|
||||
## 1.17.7
|
||||
|
||||
* Enforce polygon winding and closure rules in tippecanoe-decode
|
||||
|
||||
## 1.17.6
|
||||
|
||||
* Add tile-join options to set name, attribution, description
|
||||
|
||||
## 1.17.5
|
||||
|
||||
* Preserve the tileset names from the source mbtiles in tile-join
|
||||
|
||||
## 1.17.4
|
||||
|
||||
* Fix RFC 8142 support: Don't try to split *all* memory mapped files
|
||||
|
||||
## 1.17.3
|
||||
|
||||
* Support RFC 8142 GeoJSON text sequences
|
||||
|
||||
## 1.17.2
|
||||
|
||||
* Organize usage output the same way as in the README
|
||||
|
||||
## 1.17.1
|
||||
|
||||
* Add -T option to coerce the types of feature attributes
|
||||
|
||||
## 1.17.0
|
||||
|
||||
* Add -zg option to guess an appropriate maxzoom
|
||||
|
||||
## 1.16.17
|
||||
|
||||
* Clean up JSON parsing at the end of each FeatureCollection
|
||||
to avoid running out of memory
|
||||
|
||||
## 1.16.16
|
||||
|
||||
* Add tile-join options to include or exclude specific layers
|
||||
|
||||
## 1.16.15
|
||||
|
||||
* Add --output-to-directory and --no-tile-compression options
|
||||
|
||||
## 1.16.14
|
||||
|
||||
* Add --description option for mbtiles metadata
|
||||
* Clean up some utility functions
|
||||
|
||||
## 1.16.13
|
||||
|
||||
* Add --detect-longitude-wraparound option
|
||||
|
||||
## 1.16.12
|
||||
|
||||
* Stop processing higher zooms when a feature reaches its explicit maxzoom tag
|
||||
|
||||
## 1.16.11
|
||||
|
||||
* Remove polygon splitting, since polygon cleaning is now fast enough
|
||||
|
||||
## 1.16.10
|
||||
|
||||
* Add a tippecanoe-decode option to specify layer names
|
||||
|
||||
## 1.16.9
|
||||
|
||||
* Clean up layer name handling to fix layer merging crash
|
||||
|
||||
## 1.16.8
|
||||
|
||||
* Fix some code that could sometimes try to divide by zero
|
||||
* Add check for $TIPPECANOE_MAX_THREADS environmental variable
|
||||
|
||||
## 1.16.7
|
||||
|
||||
* Fix area of placeholders for degenerate multipolygons
|
||||
|
||||
## 1.16.6
|
||||
|
||||
* Upgrade Wagyu to 0.3.0; downgrade C++ requirement to C++ 11
|
||||
|
||||
## 1.16.5
|
||||
|
||||
* Add -z and -Z options to tippecanoe-decode
|
||||
|
||||
## 1.16.4
|
||||
|
||||
* Use Wagyu's quick_lr_clip() instead of a separate implementation
|
||||
|
||||
## 1.16.3
|
||||
|
||||
* Upgrade Wagyu to bfbf2893
|
||||
|
||||
## 1.16.2
|
||||
|
||||
* Associate attributes with the right layer when explicitly tagged
|
||||
|
||||
## 1.16.1
|
||||
|
||||
* Choose a deeper starting tile than 0/0/0 if there is one that contains
|
||||
all the features
|
||||
|
||||
## 1.16.0
|
||||
|
||||
* Switch from Clipper to Wagyu for polygon topology correction
|
||||
|
19
Dockerfile
Normal file
19
Dockerfile
Normal file
@ -0,0 +1,19 @@
|
||||
# Start from ubuntu
|
||||
FROM ubuntu:16.04
|
||||
|
||||
# Update repos and install dependencies
|
||||
RUN apt-get update \
|
||||
&& apt-get -y upgrade \
|
||||
&& apt-get -y install build-essential libsqlite3-dev zlib1g-dev
|
||||
|
||||
# Create a directory and copy in all files
|
||||
RUN mkdir -p /tmp/tippecanoe-src
|
||||
WORKDIR /tmp/tippecanoe-src
|
||||
COPY . /tmp/tippecanoe-src
|
||||
|
||||
# Build tippecanoe
|
||||
RUN make \
|
||||
&& make install
|
||||
|
||||
# Run the tests
|
||||
CMD make test
|
15
Dockerfile.centos7
Normal file
15
Dockerfile.centos7
Normal file
@ -0,0 +1,15 @@
|
||||
FROM centos:7
|
||||
|
||||
RUN yum install -y make sqlite-devel zlib-devel bash git gcc-c++
|
||||
|
||||
# Create a directory and copy in all files
|
||||
RUN mkdir -p /tmp/tippecanoe-src
|
||||
WORKDIR /tmp/tippecanoe-src
|
||||
COPY . /tmp/tippecanoe-src
|
||||
|
||||
# Build tippecanoe
|
||||
RUN make \
|
||||
&& make install
|
||||
|
||||
# Run the tests
|
||||
CMD make test
|
@ -1,4 +1,4 @@
|
||||
## [Visualizing a Month of Lightning](http://rousseau.io/2015/03/23/visualizing-a-month-of-lightning/) by Jordan Rousseau
|
||||
## [Visualizing a Month of Lightning](http://rousseau.io/2015/03/23/visualizing-a-month-of-lightning) by Jordan Rousseau
|
||||
|
||||

|
||||
|
||||
|
280
Makefile
280
Makefile
@ -7,9 +7,9 @@ SHELL = /bin/bash
|
||||
CC := $(CC)
|
||||
CXX := $(CXX)
|
||||
CFLAGS := $(CFLAGS)
|
||||
CXXFLAGS := $(CXXFLAGS) -std=c++14
|
||||
CXXFLAGS := $(CXXFLAGS) -std=c++11
|
||||
LDFLAGS := $(LDFLAGS)
|
||||
WARNING_FLAGS := -Wall -Wshadow -Wsign-compare
|
||||
WARNING_FLAGS := -Wall -Wshadow -Wsign-compare -Wextra -Wunreachable-code -Wuninitialized -Wshadow
|
||||
RELEASE_FLAGS := -O3 -DNDEBUG
|
||||
DEBUG_FLAGS := -O0 -DDEBUG -fno-inline-functions -fno-omit-frame-pointer
|
||||
|
||||
@ -19,21 +19,22 @@ else
|
||||
FINAL_FLAGS := -g $(WARNING_FLAGS) $(DEBUG_FLAGS)
|
||||
endif
|
||||
|
||||
all: tippecanoe tippecanoe-enumerate tippecanoe-decode tile-join unit
|
||||
all: tippecanoe tippecanoe-enumerate tippecanoe-decode tile-join unit tippecanoe-json-tool
|
||||
|
||||
docs: man/tippecanoe.1
|
||||
|
||||
install: tippecanoe tippecanoe-enumerate tippecanoe-decode tile-join
|
||||
install: tippecanoe tippecanoe-enumerate tippecanoe-decode tile-join tippecanoe-json-tool
|
||||
mkdir -p $(PREFIX)/bin
|
||||
mkdir -p $(MANDIR)
|
||||
cp tippecanoe $(PREFIX)/bin/tippecanoe
|
||||
cp tippecanoe-enumerate $(PREFIX)/bin/tippecanoe-enumerate
|
||||
cp tippecanoe-decode $(PREFIX)/bin/tippecanoe-decode
|
||||
cp tippecanoe-json-tool $(PREFIX)/bin/tippecanoe-json-tool
|
||||
cp tile-join $(PREFIX)/bin/tile-join
|
||||
cp man/tippecanoe.1 $(MANDIR)/tippecanoe.1
|
||||
|
||||
uninstall:
|
||||
rm $(PREFIX)/bin/tippecanoe $(PREFIX)/bin/tippecanoe-enumerate $(PREFIX)/bin/tippecanoe-decode $(PREFIX)/bin/tile-join $(MANDIR)/tippecanoe.1
|
||||
rm $(PREFIX)/bin/tippecanoe $(PREFIX)/bin/tippecanoe-enumerate $(PREFIX)/bin/tippecanoe-decode $(PREFIX)/bin/tile-join $(MANDIR)/tippecanoe.1 $(PREFIX)/bin/tippecanoe-json-tool
|
||||
|
||||
man/tippecanoe.1: README.md
|
||||
md2man-roff README.md > man/tippecanoe.1
|
||||
@ -46,16 +47,19 @@ C = $(wildcard *.c) $(wildcard *.cpp)
|
||||
INCLUDES = -I/usr/local/include -I.
|
||||
LIBS = -L/usr/local/lib
|
||||
|
||||
tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o serial.o main.o text.o
|
||||
tippecanoe: geojson.o jsonpull/jsonpull.o tile.o pool.o mbtiles.o geometry.o projection.o memfile.o mvt.o serial.o main.o text.o dirtiles.o plugin.o read_json.o write_json.o geobuf.o evaluator.o geocsv.o csv.o geojson-loop.o
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
|
||||
|
||||
tippecanoe-enumerate: enumerate.o
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CFLAGS) -o $@ $^ $(LDFLAGS) -lsqlite3
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lsqlite3
|
||||
|
||||
tippecanoe-decode: decode.o projection.o mvt.o
|
||||
tippecanoe-decode: decode.o projection.o mvt.o write_json.o text.o jsonpull/jsonpull.o dirtiles.o
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3
|
||||
|
||||
tile-join: tile-join.o projection.o pool.o mbtiles.o mvt.o memfile.o
|
||||
tile-join: tile-join.o projection.o pool.o mbtiles.o mvt.o memfile.o dirtiles.o jsonpull/jsonpull.o text.o evaluator.o csv.o write_json.o
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
|
||||
|
||||
tippecanoe-json-tool: jsontool.o jsonpull/jsonpull.o csv.o text.o geojson-loop.o
|
||||
$(CXX) $(PG) $(LIBS) $(FINAL_FLAGS) $(CXXFLAGS) -o $@ $^ $(LDFLAGS) -lm -lz -lsqlite3 -lpthread
|
||||
|
||||
unit: unit.o text.o
|
||||
@ -70,7 +74,7 @@ unit: unit.o text.o
|
||||
$(CXX) -MMD $(PG) $(INCLUDES) $(FINAL_FLAGS) $(CXXFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f ./tippecanoe ./tippecanoe-* ./tile-join ./unit *.o *.d */*.o */*.d
|
||||
rm -f ./tippecanoe ./tippecanoe-* ./tile-join ./unit *.o *.d */*.o */*.d tests/**/*.mbtiles tests/**/*.check
|
||||
|
||||
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, SortIncludes: false}" $(C) $(H)
|
||||
@ -78,71 +82,259 @@ indent:
|
||||
TESTS = $(wildcard tests/*/out/*.json)
|
||||
SPACE = $(NULL) $(NULL)
|
||||
|
||||
test: tippecanoe tippecanoe-decode $(addsuffix .check,$(TESTS)) parallel-test pbf-test join-test enumerate-test unit
|
||||
test: tippecanoe tippecanoe-decode $(addsuffix .check,$(TESTS)) raw-tiles-test parallel-test pbf-test join-test enumerate-test decode-test join-filter-test unit json-tool-test allow-existing-test csv-test layer-json-test
|
||||
./unit
|
||||
|
||||
suffixes = json json.gz
|
||||
|
||||
# Work around Makefile and filename punctuation limits: _ for space, @ for :, % for /
|
||||
%.json.check:
|
||||
./tippecanoe -aD -f -o $@.mbtiles $(subst @,:,$(subst %,/,$(subst _, ,$(patsubst %.json.check,%,$(word 4,$(subst /, ,$@)))))) $(wildcard $(subst $(SPACE),/,$(wordlist 1,2,$(subst /, ,$@)))/*.json) < /dev/null
|
||||
./tippecanoe-decode $@.mbtiles > $@.out
|
||||
cmp $(patsubst %.check,%,$@) $@.out
|
||||
./tippecanoe -q -a@ -f -o $@.mbtiles $(subst @,:,$(subst %,/,$(subst _, ,$(patsubst %.json.check,%,$(word 4,$(subst /, ,$@)))))) $(foreach suffix,$(suffixes),$(sort $(wildcard $(subst $(SPACE),/,$(wordlist 1,2,$(subst /, ,$@)))/*.$(suffix)))) < /dev/null
|
||||
./tippecanoe-decode -x generator $@.mbtiles > $@.out
|
||||
cmp $@.out $(patsubst %.check,%,$@)
|
||||
rm $@.out $@.mbtiles
|
||||
|
||||
# Don't test overflow with geobuf, because it fails (https://github.com/mapbox/geobuf/issues/87)
|
||||
# Don't test stringids with geobuf, because it fails
|
||||
nogeobuf = tests/overflow/out/-z0.json $(wildcard tests/stringid/out/*.json)
|
||||
geobuf-test: tippecanoe-json-tool $(addsuffix .checkbuf,$(filter-out $(nogeobuf),$(TESTS)))
|
||||
|
||||
# For quicker address sanitizer build, hope that regular JSON parsing is tested enough by parallel and join tests
|
||||
fewer-tests: tippecanoe tippecanoe-decode geobuf-test raw-tiles-test parallel-test pbf-test join-test enumerate-test decode-test join-filter-test unit
|
||||
|
||||
# XXX Use proper makefile rules instead of a for loop
|
||||
%.json.checkbuf:
|
||||
for i in $(wildcard $(subst $(SPACE),/,$(wordlist 1,2,$(subst /, ,$@)))/*.json); do ./tippecanoe-json-tool -w $$i | ./node_modules/geobuf/bin/json2geobuf > $$i.geobuf; done
|
||||
for i in $(wildcard $(subst $(SPACE),/,$(wordlist 1,2,$(subst /, ,$@)))/*.json.gz); do gzip -dc $$i | ./tippecanoe-json-tool -w | ./node_modules/geobuf/bin/json2geobuf > $$i.geobuf; done
|
||||
./tippecanoe -q -a@ -f -o $@.mbtiles $(subst @,:,$(subst %,/,$(subst _, ,$(patsubst %.json.checkbuf,%,$(word 4,$(subst /, ,$@)))))) $(foreach suffix,$(suffixes),$(addsuffix .geobuf,$(sort $(wildcard $(subst $(SPACE),/,$(wordlist 1,2,$(subst /, ,$@)))/*.$(suffix))))) < /dev/null
|
||||
./tippecanoe-decode -x generator $@.mbtiles | sed 's/checkbuf/check/g' | sed 's/\.geobuf//g' > $@.out
|
||||
cmp $@.out $(patsubst %.checkbuf,%,$@)
|
||||
rm $@.out $@.mbtiles
|
||||
|
||||
parallel-test:
|
||||
mkdir -p tests/parallel
|
||||
perl -e 'for ($$i = 0; $$i < 20; $$i++) { $$lon = rand(360) - 180; $$lat = rand(180) - 90; print "{ \"type\": \"Feature\", \"properties\": { \"yes\": \"no\", \"who\": 1 }, \"geometry\": { \"type\": \"Point\", \"coordinates\": [ $$lon, $$lat ] } }\n"; }' > tests/parallel/in1.json
|
||||
perl -e 'for ($$i = 0; $$i < 20; $$i++) { $$lon = rand(360) - 180; $$lat = rand(180) - 90; $$k = rand(1); $$v = rand(1); print "{ \"type\": \"Feature\", \"properties\": { \"yes\": \"no\", \"who\": 1, \"$$k\": \"$$v\" }, \"geometry\": { \"type\": \"Point\", \"coordinates\": [ $$lon, $$lat ] } }\n"; }' > tests/parallel/in1.json
|
||||
perl -e 'for ($$i = 0; $$i < 300000; $$i++) { $$lon = rand(360) - 180; $$lat = rand(180) - 90; print "{ \"type\": \"Feature\", \"properties\": { }, \"geometry\": { \"type\": \"Point\", \"coordinates\": [ $$lon, $$lat ] } }\n"; }' > tests/parallel/in2.json
|
||||
perl -e 'for ($$i = 0; $$i < 20; $$i++) { $$lon = rand(360) - 180; $$lat = rand(180) - 90; print "{ \"type\": \"Feature\", \"properties\": { }, \"geometry\": { \"type\": \"Point\", \"coordinates\": [ $$lon, $$lat ] } }\n"; }' > tests/parallel/in3.json
|
||||
perl -e 'for ($$i = 0; $$i < 20; $$i++) { $$lon = rand(360) - 180; $$lat = rand(180) - 90; $$v = rand(1); print "{ \"type\": \"Feature\", \"properties\": { }, \"tippecanoe\": { \"layer\": \"$$v\" }, \"geometry\": { \"type\": \"Point\", \"coordinates\": [ $$lon, $$lat ] } }\n"; }' > tests/parallel/in4.json
|
||||
echo -n "" > tests/parallel/empty1.json
|
||||
echo "" > tests/parallel/empty2.json
|
||||
./tippecanoe -z5 -f -pi -l test -n test -o tests/parallel/linear-file.mbtiles tests/parallel/in[123].json tests/parallel/empty[12].json
|
||||
./tippecanoe -z5 -f -pi -l test -n test -P -o tests/parallel/parallel-file.mbtiles tests/parallel/in[123].json tests/parallel/empty[12].json
|
||||
cat tests/parallel/in[123].json | ./tippecanoe -z5 -f -pi -l test -n test -o tests/parallel/linear-pipe.mbtiles
|
||||
cat tests/parallel/in[123].json | ./tippecanoe -z5 -f -pi -l test -n test -P -o tests/parallel/parallel-pipe.mbtiles
|
||||
./tippecanoe -z5 -f -pi -l test -n test -P -o tests/parallel/parallel-pipes.mbtiles <(cat tests/parallel/in1.json) <(cat tests/parallel/empty1.json) <(cat tests/parallel/empty2.json) <(cat tests/parallel/in2.json) /dev/null <(cat tests/parallel/in3.json)
|
||||
./tippecanoe-decode tests/parallel/linear-file.mbtiles > tests/parallel/linear-file.json
|
||||
./tippecanoe-decode tests/parallel/parallel-file.mbtiles > tests/parallel/parallel-file.json
|
||||
./tippecanoe-decode tests/parallel/linear-pipe.mbtiles > tests/parallel/linear-pipe.json
|
||||
./tippecanoe-decode tests/parallel/parallel-pipe.mbtiles > tests/parallel/parallel-pipe.json
|
||||
./tippecanoe-decode tests/parallel/parallel-pipes.mbtiles > tests/parallel/parallel-pipes.json
|
||||
./tippecanoe -q -z5 -f -pi -l test -n test -o tests/parallel/linear-file.mbtiles tests/parallel/in[1234].json tests/parallel/empty[12].json
|
||||
./tippecanoe -q -z5 -f -pi -l test -n test -P -o tests/parallel/parallel-file.mbtiles tests/parallel/in[1234].json tests/parallel/empty[12].json
|
||||
cat tests/parallel/in[1234].json | ./tippecanoe -q -z5 -f -pi -l test -n test -o tests/parallel/linear-pipe.mbtiles
|
||||
cat tests/parallel/in[1234].json | ./tippecanoe -q -z5 -f -pi -l test -n test -P -o tests/parallel/parallel-pipe.mbtiles
|
||||
cat tests/parallel/in[1234].json | sed 's/^/@/' | tr '@' '\036' | ./tippecanoe -q -z5 -f -pi -l test -n test -o tests/parallel/implicit-pipe.mbtiles
|
||||
./tippecanoe -q -z5 -f -pi -l test -n test -P -o tests/parallel/parallel-pipes.mbtiles <(cat tests/parallel/in1.json) <(cat tests/parallel/empty1.json) <(cat tests/parallel/empty2.json) <(cat tests/parallel/in2.json) /dev/null <(cat tests/parallel/in3.json) <(cat tests/parallel/in4.json)
|
||||
./tippecanoe-decode -x generator -x generator_options tests/parallel/linear-file.mbtiles > tests/parallel/linear-file.json
|
||||
./tippecanoe-decode -x generator -x generator_options tests/parallel/parallel-file.mbtiles > tests/parallel/parallel-file.json
|
||||
./tippecanoe-decode -x generator -x generator_options tests/parallel/linear-pipe.mbtiles > tests/parallel/linear-pipe.json
|
||||
./tippecanoe-decode -x generator -x generator_options tests/parallel/parallel-pipe.mbtiles > tests/parallel/parallel-pipe.json
|
||||
./tippecanoe-decode -x generator -x generator_options tests/parallel/implicit-pipe.mbtiles > tests/parallel/implicit-pipe.json
|
||||
./tippecanoe-decode -x generator -x generator_options tests/parallel/parallel-pipes.mbtiles > tests/parallel/parallel-pipes.json
|
||||
cmp tests/parallel/linear-file.json tests/parallel/parallel-file.json
|
||||
cmp tests/parallel/linear-file.json tests/parallel/linear-pipe.json
|
||||
cmp tests/parallel/linear-file.json tests/parallel/parallel-pipe.json
|
||||
cmp tests/parallel/linear-file.json tests/parallel/implicit-pipe.json
|
||||
cmp tests/parallel/linear-file.json tests/parallel/parallel-pipes.json
|
||||
rm tests/parallel/*.mbtiles tests/parallel/*.json
|
||||
|
||||
raw-tiles-test:
|
||||
./tippecanoe -q -f -e tests/raw-tiles/raw-tiles -r1 -pC tests/raw-tiles/hackspots.geojson
|
||||
./tippecanoe-decode -x generator tests/raw-tiles/raw-tiles > tests/raw-tiles/raw-tiles.json.check
|
||||
cmp tests/raw-tiles/raw-tiles.json.check tests/raw-tiles/raw-tiles.json
|
||||
# Test that -z and -Z work in tippecanoe-decode
|
||||
./tippecanoe-decode -x generator -Z6 -z7 tests/raw-tiles/raw-tiles > tests/raw-tiles/raw-tiles-z67.json.check
|
||||
cmp tests/raw-tiles/raw-tiles-z67.json.check tests/raw-tiles/raw-tiles-z67.json
|
||||
# Test that -z and -Z work in tile-join
|
||||
./tile-join -q -f -Z6 -z7 -e tests/raw-tiles/raw-tiles-z67 tests/raw-tiles/raw-tiles
|
||||
./tippecanoe-decode -x generator tests/raw-tiles/raw-tiles-z67 > tests/raw-tiles/raw-tiles-z67-join.json.check
|
||||
cmp tests/raw-tiles/raw-tiles-z67-join.json.check tests/raw-tiles/raw-tiles-z67-join.json
|
||||
rm -rf tests/raw-tiles/raw-tiles tests/raw-tiles/raw-tiles-z67 tests/raw-tiles/raw-tiles.json.check raw-tiles-z67.json.check tests/raw-tiles/raw-tiles-z67-join.json.check
|
||||
# Test that metadata.json is created even if all features are clipped away
|
||||
./tippecanoe -q -f -e tests/raw-tiles/nothing tests/raw-tiles/nothing.geojson
|
||||
./tippecanoe-decode -x generator tests/raw-tiles/nothing > tests/raw-tiles/nothing.json.check
|
||||
cmp tests/raw-tiles/nothing.json.check tests/raw-tiles/nothing.json
|
||||
rm -r tests/raw-tiles/nothing tests/raw-tiles/nothing.json.check
|
||||
|
||||
decode-test:
|
||||
mkdir -p tests/muni/decode
|
||||
./tippecanoe -q -z11 -Z11 -f -o tests/muni/decode/multi.mbtiles tests/muni/*.json
|
||||
./tippecanoe-decode -x generator -l subway tests/muni/decode/multi.mbtiles > tests/muni/decode/multi.mbtiles.json.check
|
||||
./tippecanoe-decode -x generator -c tests/muni/decode/multi.mbtiles > tests/muni/decode/multi.mbtiles.pipeline.json.check
|
||||
./tippecanoe-decode -x generator tests/muni/decode/multi.mbtiles 11 327 791 > tests/muni/decode/multi.mbtiles.onetile.json.check
|
||||
./tippecanoe-decode -x generator --stats tests/muni/decode/multi.mbtiles > tests/muni/decode/multi.mbtiles.stats.json.check
|
||||
cmp tests/muni/decode/multi.mbtiles.json.check tests/muni/decode/multi.mbtiles.json
|
||||
cmp tests/muni/decode/multi.mbtiles.pipeline.json.check tests/muni/decode/multi.mbtiles.pipeline.json
|
||||
cmp tests/muni/decode/multi.mbtiles.onetile.json.check tests/muni/decode/multi.mbtiles.onetile.json
|
||||
cmp tests/muni/decode/multi.mbtiles.stats.json.check tests/muni/decode/multi.mbtiles.stats.json
|
||||
rm -f tests/muni/decode/multi.mbtiles.json.check tests/muni/decode/multi.mbtiles tests/muni/decode/multi.mbtiles.pipeline.json.check tests/muni/decode/multi.mbtiles.stats.json.check tests/muni/decode/multi.mbtiles.onetile.json.check
|
||||
|
||||
pbf-test:
|
||||
./tippecanoe-decode tests/pbf/11-328-791.vector.pbf 11 328 791 > tests/pbf/11-328-791.vector.pbf.out
|
||||
./tippecanoe-decode -x generator tests/pbf/11-328-791.vector.pbf 11 328 791 > tests/pbf/11-328-791.vector.pbf.out
|
||||
cmp tests/pbf/11-328-791.json tests/pbf/11-328-791.vector.pbf.out
|
||||
rm tests/pbf/11-328-791.vector.pbf.out
|
||||
./tippecanoe-decode -t EPSG:3857 tests/pbf/11-328-791.vector.pbf 11 328 791 > tests/pbf/11-328-791.3857.vector.pbf.out
|
||||
./tippecanoe-decode -x generator -s EPSG:3857 tests/pbf/11-328-791.vector.pbf 11 328 791 > tests/pbf/11-328-791.3857.vector.pbf.out
|
||||
cmp tests/pbf/11-328-791.3857.json tests/pbf/11-328-791.3857.vector.pbf.out
|
||||
rm tests/pbf/11-328-791.3857.vector.pbf.out
|
||||
|
||||
enumerate-test:
|
||||
./tippecanoe -z5 -f -o tests/ne_110m_admin_0_countries/out/enum.mbtiles tests/ne_110m_admin_0_countries/in.json
|
||||
./tippecanoe -q -z5 -f -o tests/ne_110m_admin_0_countries/out/enum.mbtiles tests/ne_110m_admin_0_countries/in.json.gz
|
||||
./tippecanoe-enumerate tests/ne_110m_admin_0_countries/out/enum.mbtiles > tests/ne_110m_admin_0_countries/out/enum.check
|
||||
cmp tests/ne_110m_admin_0_countries/out/enum tests/ne_110m_admin_0_countries/out/enum.check
|
||||
rm tests/ne_110m_admin_0_countries/out/enum.mbtiles tests/ne_110m_admin_0_countries/out/enum.check
|
||||
|
||||
join-test:
|
||||
./tippecanoe -f -z12 -o tests/join-population/tabblock_06001420.mbtiles tests/join-population/tabblock_06001420.json
|
||||
./tippecanoe -f -z12 -o tests/join-population/tabblock_06001420.mbtiles tests/join-population/tabblock_06001420.json
|
||||
./tippecanoe -f -Z5 -z10 -o tests/join-population/macarthur.mbtiles -l macarthur tests/join-population/macarthur.json
|
||||
./tippecanoe -f -d10 -D10 -Z9 -z11 -o tests/join-population/macarthur2.mbtiles -l macarthur tests/join-population/macarthur2.json
|
||||
./tile-join -f -o tests/join-population/joined.mbtiles -x GEOID10 -c tests/join-population/population.csv tests/join-population/tabblock_06001420.mbtiles
|
||||
./tile-join -f -i -o tests/join-population/joined-i.mbtiles -x GEOID10 -c tests/join-population/population.csv tests/join-population/tabblock_06001420.mbtiles
|
||||
./tile-join -f -o tests/join-population/merged.mbtiles tests/join-population/tabblock_06001420.mbtiles tests/join-population/macarthur.mbtiles tests/join-population/macarthur2.mbtiles
|
||||
./tile-join -f -c tests/join-population/windows.csv -o tests/join-population/windows.mbtiles tests/join-population/macarthur.mbtiles
|
||||
./tippecanoe-decode tests/join-population/joined.mbtiles > tests/join-population/joined.mbtiles.json.check
|
||||
./tippecanoe-decode tests/join-population/joined-i.mbtiles > tests/join-population/joined-i.mbtiles.json.check
|
||||
./tippecanoe-decode tests/join-population/merged.mbtiles > tests/join-population/merged.mbtiles.json.check
|
||||
./tippecanoe-decode tests/join-population/windows.mbtiles > tests/join-population/windows.mbtiles.json.check
|
||||
join-test: tile-join
|
||||
./tippecanoe -q -f -z12 -o tests/join-population/tabblock_06001420.mbtiles -YALAND10:'Land area' -L'{"file": "tests/join-population/tabblock_06001420.json", "description": "population"}'
|
||||
./tippecanoe -q -f -Z5 -z10 -o tests/join-population/macarthur.mbtiles -l macarthur tests/join-population/macarthur.json
|
||||
./tile-join -q -f -Z6 -z9 -o tests/join-population/macarthur-6-9.mbtiles tests/join-population/macarthur.mbtiles
|
||||
./tippecanoe-decode -x generator tests/join-population/macarthur-6-9.mbtiles > tests/join-population/macarthur-6-9.mbtiles.json.check
|
||||
cmp tests/join-population/macarthur-6-9.mbtiles.json.check tests/join-population/macarthur-6-9.mbtiles.json
|
||||
./tile-join -q -f -Z6 -z9 -X -o tests/join-population/macarthur-6-9-exclude.mbtiles tests/join-population/macarthur.mbtiles
|
||||
./tippecanoe-decode -x generator tests/join-population/macarthur-6-9-exclude.mbtiles > tests/join-population/macarthur-6-9-exclude.mbtiles.json.check
|
||||
cmp tests/join-population/macarthur-6-9-exclude.mbtiles.json.check tests/join-population/macarthur-6-9-exclude.mbtiles.json
|
||||
rm -f tests/join-population/macarthur-6-9.mbtiles.json.check tests/join-population/macarthur-6-9.mbtiles tests/join-population/macarthur-6-9-exclude.mbtiles.json.check tests/join-population/macarthur-6-9-exclude.mbtiles
|
||||
./tippecanoe -q -f -d10 -D10 -Z9 -z11 -o tests/join-population/macarthur2.mbtiles -l macarthur tests/join-population/macarthur2.json
|
||||
./tile-join --quiet --force -o tests/join-population/joined.mbtiles -x GEOID10 -c tests/join-population/population.csv tests/join-population/tabblock_06001420.mbtiles
|
||||
./tile-join --quiet --force -o tests/join-population/joined-null.mbtiles --empty-csv-columns-are-null -x GEOID10 -c tests/join-population/population.csv tests/join-population/tabblock_06001420.mbtiles
|
||||
./tile-join --quiet --force --no-tile-stats -o tests/join-population/joined-no-tile-stats.mbtiles -x GEOID10 -c tests/join-population/population.csv tests/join-population/tabblock_06001420.mbtiles
|
||||
./tile-join -q -f -i -o tests/join-population/joined-i.mbtiles -x GEOID10 -c tests/join-population/population.csv tests/join-population/tabblock_06001420.mbtiles
|
||||
./tile-join -q -f -o tests/join-population/merged.mbtiles tests/join-population/tabblock_06001420.mbtiles tests/join-population/macarthur.mbtiles tests/join-population/macarthur2.mbtiles
|
||||
./tile-join -q -f -c tests/join-population/windows.csv -o tests/join-population/windows.mbtiles tests/join-population/macarthur.mbtiles
|
||||
./tippecanoe-decode -x generator --maximum-zoom=11 --minimum-zoom=4 tests/join-population/joined.mbtiles > tests/join-population/joined.mbtiles.json.check
|
||||
./tippecanoe-decode -x generator --maximum-zoom=11 --minimum-zoom=4 tests/join-population/joined-null.mbtiles > tests/join-population/joined-null.mbtiles.json.check
|
||||
./tippecanoe-decode -x generator --maximum-zoom=11 --minimum-zoom=4 tests/join-population/joined-no-tile-stats.mbtiles > tests/join-population/joined-no-tile-stats.mbtiles.json.check
|
||||
./tippecanoe-decode -x generator tests/join-population/joined-i.mbtiles > tests/join-population/joined-i.mbtiles.json.check
|
||||
./tippecanoe-decode -x generator tests/join-population/merged.mbtiles > tests/join-population/merged.mbtiles.json.check
|
||||
./tippecanoe-decode -x generator tests/join-population/windows.mbtiles > tests/join-population/windows.mbtiles.json.check
|
||||
cmp tests/join-population/joined.mbtiles.json.check tests/join-population/joined.mbtiles.json
|
||||
cmp tests/join-population/joined-null.mbtiles.json.check tests/join-population/joined-null.mbtiles.json
|
||||
cmp tests/join-population/joined-no-tile-stats.mbtiles.json.check tests/join-population/joined-no-tile-stats.mbtiles.json
|
||||
cmp tests/join-population/joined-i.mbtiles.json.check tests/join-population/joined-i.mbtiles.json
|
||||
cmp tests/join-population/merged.mbtiles.json.check tests/join-population/merged.mbtiles.json
|
||||
cmp tests/join-population/windows.mbtiles.json.check tests/join-population/windows.mbtiles.json
|
||||
rm tests/join-population/tabblock_06001420.mbtiles tests/join-population/joined.mbtiles tests/join-population/joined-i.mbtiles tests/join-population/joined.mbtiles.json.check tests/join-population/joined-i.mbtiles.json.check tests/join-population/macarthur.mbtiles tests/join-population/merged.mbtiles tests/join-population/merged.mbtiles.json.check tests/join-population/macarthur2.mbtiles tests/join-population/windows.mbtiles tests/join-population/windows.mbtiles.json.check
|
||||
rm -f tests/join-population/joined-null.mbtiles tests/join-population/joined-null.mbtiles.json.check
|
||||
./tile-join -q -f -l macarthur -n "macarthur name" -N "macarthur description" -A "macarthur's attribution" -o tests/join-population/just-macarthur.mbtiles tests/join-population/merged.mbtiles
|
||||
./tile-join -q -f -L macarthur -o tests/join-population/no-macarthur.mbtiles tests/join-population/merged.mbtiles
|
||||
./tippecanoe-decode -x generator tests/join-population/just-macarthur.mbtiles > tests/join-population/just-macarthur.mbtiles.json.check
|
||||
./tippecanoe-decode -x generator tests/join-population/no-macarthur.mbtiles > tests/join-population/no-macarthur.mbtiles.json.check
|
||||
cmp tests/join-population/just-macarthur.mbtiles.json.check tests/join-population/just-macarthur.mbtiles.json
|
||||
cmp tests/join-population/no-macarthur.mbtiles.json.check tests/join-population/no-macarthur.mbtiles.json
|
||||
./tile-join -q --no-tile-compression -f -e tests/join-population/raw-merged-folder tests/join-population/tabblock_06001420.mbtiles tests/join-population/macarthur.mbtiles tests/join-population/macarthur2.mbtiles
|
||||
./tippecanoe-decode -x generator tests/join-population/raw-merged-folder > tests/join-population/raw-merged-folder.json.check
|
||||
cmp tests/join-population/raw-merged-folder.json.check tests/join-population/raw-merged-folder.json
|
||||
rm -f tests/join-population/raw-merged-folder.json.check
|
||||
./tippecanoe -q -z12 -f -e tests/join-population/tabblock_06001420-folder -YALAND10:'Land area' -L'{"file": "tests/join-population/tabblock_06001420.json", "description": "population"}'
|
||||
./tippecanoe -q -Z5 -z10 -f -e tests/join-population/macarthur-folder -l macarthur tests/join-population/macarthur.json
|
||||
./tippecanoe -q -d10 -D10 -Z9 -z11 -f -e tests/join-population/macarthur2-folder -l macarthur tests/join-population/macarthur2.json
|
||||
./tile-join -q -f -o tests/join-population/merged-folder.mbtiles tests/join-population/tabblock_06001420-folder tests/join-population/macarthur-folder tests/join-population/macarthur2-folder
|
||||
./tippecanoe-decode -x generator tests/join-population/merged-folder.mbtiles > tests/join-population/merged-folder.mbtiles.json.check
|
||||
cmp tests/join-population/merged-folder.mbtiles.json.check tests/join-population/merged-folder.mbtiles.json
|
||||
./tile-join -q -n "merged name" -N "merged description" -f -e tests/join-population/merged-mbtiles-to-folder tests/join-population/tabblock_06001420.mbtiles tests/join-population/macarthur.mbtiles tests/join-population/macarthur2.mbtiles
|
||||
./tile-join -q -n "merged name" -N "merged description" -f -e tests/join-population/merged-folders-to-folder tests/join-population/tabblock_06001420-folder tests/join-population/macarthur-folder tests/join-population/macarthur2-folder
|
||||
./tippecanoe-decode -x generator -x generator_options tests/join-population/merged-mbtiles-to-folder > tests/join-population/merged-mbtiles-to-folder.json.check
|
||||
./tippecanoe-decode -x generator -x generator_options tests/join-population/merged-folders-to-folder > tests/join-population/merged-folders-to-folder.json.check
|
||||
cmp tests/join-population/merged-mbtiles-to-folder.json.check tests/join-population/merged-folders-to-folder.json.check
|
||||
rm -f tests/join-population/merged-mbtiles-to-folder.json.check tests/join-population/merged-folders-to-folder.json.check
|
||||
./tile-join -q -f -c tests/join-population/windows.csv -o tests/join-population/windows-merged.mbtiles tests/join-population/macarthur.mbtiles tests/join-population/macarthur2-folder
|
||||
./tile-join -q -c tests/join-population/windows.csv -f -e tests/join-population/windows-merged-folder tests/join-population/macarthur.mbtiles tests/join-population/macarthur2-folder
|
||||
./tile-join -q -f -o tests/join-population/windows-merged2.mbtiles tests/join-population/windows-merged-folder
|
||||
./tippecanoe-decode -x generator -x generator_options tests/join-population/windows-merged.mbtiles > tests/join-population/windows-merged.mbtiles.json.check
|
||||
./tippecanoe-decode -x generator -x generator_options tests/join-population/windows-merged2.mbtiles > tests/join-population/windows-merged2.mbtiles.json.check
|
||||
cmp tests/join-population/windows-merged.mbtiles.json.check tests/join-population/windows-merged2.mbtiles.json.check
|
||||
./tile-join -q -f -o tests/join-population/macarthur-and-macarthur2-merged.mbtiles tests/join-population/macarthur.mbtiles tests/join-population/macarthur2-folder
|
||||
./tile-join -q -f -e tests/join-population/macarthur-and-macarthur2-folder tests/join-population/macarthur.mbtiles tests/join-population/macarthur2-folder
|
||||
./tile-join -q -f -o tests/join-population/macarthur-and-macarthur2-merged2.mbtiles tests/join-population/macarthur-and-macarthur2-folder
|
||||
./tippecanoe-decode -x generator -x generator_options tests/join-population/macarthur-and-macarthur2-merged.mbtiles > tests/join-population/macarthur-and-macarthur2-merged.mbtiles.json.check
|
||||
./tippecanoe-decode -x generator -x generator_options tests/join-population/macarthur-and-macarthur2-merged2.mbtiles > tests/join-population/macarthur-and-macarthur2-merged2.mbtiles.json.check
|
||||
cmp tests/join-population/macarthur-and-macarthur2-merged.mbtiles.json.check tests/join-population/macarthur-and-macarthur2-merged2.mbtiles.json.check
|
||||
rm tests/join-population/tabblock_06001420.mbtiles tests/join-population/joined.mbtiles tests/join-population/joined-i.mbtiles tests/join-population/joined.mbtiles.json.check tests/join-population/joined-i.mbtiles.json.check tests/join-population/macarthur.mbtiles tests/join-population/merged.mbtiles tests/join-population/merged.mbtiles.json.check tests/join-population/merged-folder.mbtiles tests/join-population/macarthur2.mbtiles tests/join-population/windows.mbtiles tests/join-population/windows-merged.mbtiles tests/join-population/windows-merged2.mbtiles tests/join-population/windows.mbtiles.json.check tests/join-population/just-macarthur.mbtiles tests/join-population/no-macarthur.mbtiles tests/join-population/just-macarthur.mbtiles.json.check tests/join-population/no-macarthur.mbtiles.json.check tests/join-population/merged-folder.mbtiles.json.check tests/join-population/windows-merged.mbtiles.json.check tests/join-population/windows-merged2.mbtiles.json.check tests/join-population/macarthur-and-macarthur2-merged.mbtiles tests/join-population/macarthur-and-macarthur2-merged2.mbtiles tests/join-population/macarthur-and-macarthur2-merged.mbtiles.json.check tests/join-population/macarthur-and-macarthur2-merged2.mbtiles.json.check
|
||||
rm -rf tests/join-population/raw-merged-folder tests/join-population/tabblock_06001420-folder tests/join-population/macarthur-folder tests/join-population/macarthur2-folder tests/join-population/merged-mbtiles-to-folder tests/join-population/merged-folders-to-folder tests/join-population/windows-merged-folder tests/join-population/macarthur-and-macarthur2-folder
|
||||
# Test renaming of layers
|
||||
./tippecanoe -q -f -Z5 -z10 -o tests/join-population/macarthur.mbtiles -l macarthur1 tests/join-population/macarthur.json
|
||||
./tippecanoe -q -f -Z5 -z10 -o tests/join-population/macarthur2.mbtiles -l macarthur2 tests/join-population/macarthur2.json
|
||||
./tile-join -q -R macarthur1:one --rename-layer=macarthur2:two -f -o tests/join-population/renamed.mbtiles tests/join-population/macarthur.mbtiles tests/join-population/macarthur2.mbtiles
|
||||
./tippecanoe-decode -x generator tests/join-population/renamed.mbtiles > tests/join-population/renamed.mbtiles.json.check
|
||||
cmp tests/join-population/renamed.mbtiles.json.check tests/join-population/renamed.mbtiles.json
|
||||
rm -f tests/join-population/renamed.mbtiles.json.check tests/join-population/renamed.mbtiles.json.check tests/join-population/macarthur.mbtiles tests/join-population/macarthur2.mbtiles
|
||||
# Make sure the concatenated name isn't too long
|
||||
./tippecanoe -q -f -z0 -n 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' -o tests/join-population/macarthur.mbtiles tests/join-population/macarthur.json
|
||||
./tile-join -f -o tests/join-population/concat.mbtiles tests/join-population/macarthur.mbtiles tests/join-population/macarthur.mbtiles tests/join-population/macarthur.mbtiles tests/join-population/macarthur.mbtiles tests/join-population/macarthur.mbtiles tests/join-population/macarthur.mbtiles
|
||||
./tippecanoe-decode -x generator tests/join-population/concat.mbtiles > tests/join-population/concat.mbtiles.json.check
|
||||
cmp tests/join-population/concat.mbtiles.json.check tests/join-population/concat.mbtiles.json
|
||||
rm tests/join-population/concat.mbtiles.json.check tests/join-population/concat.mbtiles tests/join-population/macarthur.mbtiles
|
||||
|
||||
join-filter-test:
|
||||
# Comes out different from the direct tippecanoe run because null attributes are lost
|
||||
./tippecanoe -q -z0 -f -o tests/feature-filter/out/all.mbtiles tests/feature-filter/in.json
|
||||
./tile-join -q -J tests/feature-filter/filter -f -o tests/feature-filter/out/filtered.mbtiles tests/feature-filter/out/all.mbtiles
|
||||
./tippecanoe-decode -x generator tests/feature-filter/out/filtered.mbtiles > tests/feature-filter/out/filtered.json.check
|
||||
cmp tests/feature-filter/out/filtered.json.check tests/feature-filter/out/filtered.json.standard
|
||||
rm -f tests/feature-filter/out/filtered.json.check tests/feature-filter/out/filtered.mbtiles tests/feature-filter/out/all.mbtiles
|
||||
# Test zoom level filtering
|
||||
./tippecanoe -q -r1 -z8 -f -o tests/feature-filter/out/places.mbtiles tests/ne_110m_populated_places/in.json
|
||||
./tile-join -q -J tests/feature-filter/places-filter -f -o tests/feature-filter/out/places-filter.mbtiles tests/feature-filter/out/places.mbtiles
|
||||
./tippecanoe-decode -x generator tests/feature-filter/out/places-filter.mbtiles > tests/feature-filter/out/places-filter.mbtiles.json.check
|
||||
cmp tests/feature-filter/out/places-filter.mbtiles.json.check tests/feature-filter/out/places-filter.mbtiles.json.standard
|
||||
rm -f tests/feature-filter/out/places.mbtiles tests/feature-filter/out/places-filter.mbtiles tests/feature-filter/out/places-filter.mbtiles.json.check
|
||||
|
||||
json-tool-test: tippecanoe-json-tool
|
||||
./tippecanoe-json-tool -e GEOID10 tests/join-population/tabblock_06001420.json | sort > tests/join-population/tabblock_06001420.json.sort
|
||||
./tippecanoe-json-tool -c tests/join-population/population.csv tests/join-population/tabblock_06001420.json.sort > tests/join-population/tabblock_06001420.json.sort.joined
|
||||
./tippecanoe-json-tool --empty-csv-columns-are-null -c tests/join-population/population.csv tests/join-population/tabblock_06001420.json.sort > tests/join-population/tabblock_06001420-null.json.sort.joined
|
||||
cmp tests/join-population/tabblock_06001420.json.sort.joined tests/join-population/tabblock_06001420.json.sort.joined.standard
|
||||
cmp tests/join-population/tabblock_06001420-null.json.sort.joined tests/join-population/tabblock_06001420-null.json.sort.joined.standard
|
||||
rm -f tests/join-population/tabblock_06001420.json.sort tests/join-population/tabblock_06001420.json.sort.joined
|
||||
rm -f tests/join-population/tabblock_06001420-null.json.sort.joined
|
||||
|
||||
allow-existing-test:
|
||||
# Make a tileset
|
||||
./tippecanoe -q -Z0 -z0 -f -o tests/allow-existing/both.mbtiles tests/coalesce-tract/tl_2010_06001_tract10.json
|
||||
# Writing to existing should fail
|
||||
if ./tippecanoe -q -Z1 -z1 -o tests/allow-existing/both.mbtiles tests/coalesce-tract/tl_2010_06001_tract10.json; then exit 1; else exit 0; fi
|
||||
# Replace existing
|
||||
./tippecanoe -q -Z8 -z9 -f -o tests/allow-existing/both.mbtiles tests/coalesce-tract/tl_2010_06001_tract10.json
|
||||
./tippecanoe -q -Z10 -z11 -F -o tests/allow-existing/both.mbtiles tests/coalesce-tract/tl_2010_06001_tract10.json
|
||||
./tippecanoe-decode -x generator -x generator_options tests/allow-existing/both.mbtiles > tests/allow-existing/both.mbtiles.json.check
|
||||
cmp tests/allow-existing/both.mbtiles.json.check tests/allow-existing/both.mbtiles.json
|
||||
# Make a tileset
|
||||
./tippecanoe -q -Z0 -z0 -f -e tests/allow-existing/both.dir tests/coalesce-tract/tl_2010_06001_tract10.json
|
||||
# Writing to existing should fail
|
||||
if ./tippecanoe -q -Z1 -z1 -e tests/allow-existing/both.dir tests/coalesce-tract/tl_2010_06001_tract10.json; then exit 1; else exit 0; fi
|
||||
# Replace existing
|
||||
./tippecanoe -q -Z8 -z9 -f -e tests/allow-existing/both.dir tests/coalesce-tract/tl_2010_06001_tract10.json
|
||||
./tippecanoe -q -Z10 -z11 -F -e tests/allow-existing/both.dir tests/coalesce-tract/tl_2010_06001_tract10.json
|
||||
./tippecanoe-decode -x generator -x generator_options tests/allow-existing/both.dir | sed 's/both\.dir/both.mbtiles/g' > tests/allow-existing/both.dir.json.check
|
||||
cmp tests/allow-existing/both.dir.json.check tests/allow-existing/both.mbtiles.json
|
||||
rm -r tests/allow-existing/both.dir.json.check tests/allow-existing/both.dir tests/allow-existing/both.mbtiles.json.check tests/allow-existing/both.mbtiles
|
||||
|
||||
csv-test:
|
||||
# Reading from named CSV
|
||||
./tippecanoe -q -zg -f -o tests/csv/out.mbtiles tests/csv/ne_110m_populated_places_simple.csv
|
||||
./tippecanoe-decode -x generator -x generator_options tests/csv/out.mbtiles > tests/csv/out.mbtiles.json.check
|
||||
cmp tests/csv/out.mbtiles.json.check tests/csv/out.mbtiles.json
|
||||
rm -f tests/csv/out.mbtiles.json.check tests/csv/out.mbtiles
|
||||
# Reading from named CSV, with nulls
|
||||
./tippecanoe -q --empty-csv-columns-are-null -zg -f -o tests/csv/out-null.mbtiles tests/csv/ne_110m_populated_places_simple.csv
|
||||
./tippecanoe-decode -x generator tests/csv/out-null.mbtiles > tests/csv/out-null.mbtiles.json.check
|
||||
cmp tests/csv/out-null.mbtiles.json.check tests/csv/out-null.mbtiles.json
|
||||
rm -f tests/csv/out-null.mbtiles.json.check tests/csv/out-null.mbtiles
|
||||
# Same, but specifying csv with -L format
|
||||
./tippecanoe -q -zg -f -o tests/csv/out.mbtiles -L'{"file":"", "format":"csv", "layer":"ne_110m_populated_places_simple"}' < tests/csv/ne_110m_populated_places_simple.csv
|
||||
./tippecanoe-decode -x generator -x generator_options tests/csv/out.mbtiles > tests/csv/out.mbtiles.json.check
|
||||
cmp tests/csv/out.mbtiles.json.check tests/csv/out.mbtiles.json
|
||||
rm -f tests/csv/out.mbtiles.json.check tests/csv/out.mbtiles
|
||||
|
||||
layer-json-test:
|
||||
# GeoJSON with description and named layer
|
||||
./tippecanoe -q -z0 -r1 -yNAME -f -o tests/layer-json/out.mbtiles -L'{"file":"tests/ne_110m_populated_places/in.json", "description":"World cities", "layer":"places"}'
|
||||
./tippecanoe-decode -x generator -x generator_options tests/layer-json/out.mbtiles > tests/layer-json/out.mbtiles.json.check
|
||||
cmp tests/layer-json/out.mbtiles.json.check tests/layer-json/out.mbtiles.json
|
||||
rm -f tests/layer-json/out.mbtiles.json.check tests/layer-json/out.mbtiles
|
||||
# Same, but reading from the standard input
|
||||
./tippecanoe -q -z0 -r1 -yNAME -f -o tests/layer-json/out.mbtiles -L'{"file":"", "description":"World cities", "layer":"places"}' < tests/ne_110m_populated_places/in.json
|
||||
./tippecanoe-decode -x generator -x generator_options tests/layer-json/out.mbtiles > tests/layer-json/out.mbtiles.json.check
|
||||
cmp tests/layer-json/out.mbtiles.json.check tests/layer-json/out.mbtiles.json
|
||||
rm -f tests/layer-json/out.mbtiles.json.check tests/layer-json/out.mbtiles
|
||||
|
||||
# Use this target to regenerate the standards that the tests are compared against
|
||||
# after making a change that legitimately changes their output
|
||||
@ -150,7 +342,7 @@ join-test:
|
||||
prep-test: $(TESTS)
|
||||
|
||||
tests/%.json: Makefile tippecanoe tippecanoe-decode
|
||||
./tippecanoe -f -o $@.check.mbtiles $(subst @,:,$(subst %,/,$(subst _, ,$(patsubst %.json,%,$(word 4,$(subst /, ,$@)))))) $(wildcard $(subst $(SPACE),/,$(wordlist 1,2,$(subst /, ,$@)))/*.json)
|
||||
./tippecanoe-decode $@.check.mbtiles > $@
|
||||
./tippecanoe -q -a@ -f -o $@.check.mbtiles $(subst @,:,$(subst %,/,$(subst _, ,$(patsubst %.json,%,$(word 4,$(subst /, ,$@)))))) $(foreach suffix,$(suffixes),$(sort $(wildcard $(subst $(SPACE),/,$(wordlist 1,2,$(subst /, ,$@)))/*.$(suffix))))
|
||||
./tippecanoe-decode -x generator $@.check.mbtiles > $@
|
||||
cmp $(patsubst %.check,%,$@) $@
|
||||
rm $@.check.mbtiles
|
||||
|
800
README.md
800
README.md
@ -1,11 +1,30 @@
|
||||
tippecanoe
|
||||
==========
|
||||
|
||||
Builds [vector tilesets](https://www.mapbox.com/developers/vector-tiles/) from large (or small) collections of [GeoJSON](http://geojson.org/) features,
|
||||
**Note**: there is an active fork of this project over at https://github.com/felt/tippecanoe
|
||||
|
||||
Builds [vector tilesets](https://www.mapbox.com/developers/vector-tiles/) from large (or small) collections of [GeoJSON](http://geojson.org/), [Geobuf](https://github.com/mapbox/geobuf), or [CSV](https://en.wikipedia.org/wiki/Comma-separated_values) features,
|
||||
[like these](MADE_WITH.md).
|
||||
|
||||

|
||||
|
||||
[](https://travis-ci.org/mapbox/tippecanoe)
|
||||
[](https://coveralls.io/github/mapbox/tippecanoe?branch=master)
|
||||
[](https://codecov.io/gh/mapbox/tippecanoe)
|
||||
|
||||
### :zap: Mapbox has a new service for creating vector tilesets! :zap:
|
||||
|
||||
[Mapbox Tiling Service (MTS)](https://docs.mapbox.com/mapbox-tiling-service/overview/) is a hosted, data processing service that allows you to integrate custom datasets of any scale into your maps faster, cheaper, and with more flexibility and control than previously possible.
|
||||
|
||||
MTS is the same service we use internally to create our global, daily updating basemap product Mapbox Streets, which serves over 650 million monthly active users and customers such as Facebook, Snap, the Weather Channel, Tableau, and Shopify.
|
||||
|
||||
MTS creates and updates data using distributed and parallelized processing, meaning data is processed much more quickly than is possible with a standard, single server setup with comparable tools. For example, a global basemap at 30cm precision (max zoom of 16) can be processed in under 2 hours with MTS, whereas a comparable workload would take multiple days to process on a single server.
|
||||
|
||||
Customers like AllTrails, Plume Labs, and Ookla have noted that MTS helps them:
|
||||
- build applications faster by focusing more on app development, not infrastructure
|
||||
- build more compelling user experiences that drive better user engagement
|
||||
- get updated data to their users faster—in some cases up to 90% faster than previous tools
|
||||
|
||||
Learn more about [MTS](https://blog.mapbox.com/introducing-mapbox-tiling-service-df1df636c7cf).
|
||||
|
||||
Intent
|
||||
------
|
||||
@ -33,15 +52,28 @@ Installation
|
||||
|
||||
The easiest way to install tippecanoe on OSX is with [Homebrew](http://brew.sh/):
|
||||
|
||||
```js
|
||||
```sh
|
||||
$ brew install tippecanoe
|
||||
```
|
||||
|
||||
On Ubuntu it will usually be easiest to build from the source repository:
|
||||
|
||||
```sh
|
||||
$ git clone https://github.com/mapbox/tippecanoe.git
|
||||
$ cd tippecanoe
|
||||
$ make -j
|
||||
$ make install
|
||||
```
|
||||
|
||||
See [Development](#development) below for how to upgrade your
|
||||
C++ compiler or install prerequisite packages if you get
|
||||
compiler errors.
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
```sh
|
||||
$ tippecanoe -o file.mbtiles [file.json ...]
|
||||
$ tippecanoe -o file.mbtiles [options] [file.json file.json.gz file.geobuf ...]
|
||||
```
|
||||
|
||||
If no files are specified, it reads GeoJSON from the standard input.
|
||||
@ -52,112 +84,525 @@ You can concatenate multiple GeoJSON features or files together,
|
||||
and it will parse out the features and ignore whatever other objects
|
||||
it encounters.
|
||||
|
||||
Try this first
|
||||
--------------
|
||||
|
||||
If you aren't sure what options to use, try this:
|
||||
|
||||
```sh
|
||||
$ tippecanoe -zg -o out.mbtiles --drop-densest-as-needed in.geojson
|
||||
```
|
||||
|
||||
The `-zg` option will make Tippecanoe choose a maximum zoom level that should be
|
||||
high enough to reflect the precision of the original data. (If it turns out still
|
||||
not to be as detailed as you want, use `-z` manually with a higher number.)
|
||||
|
||||
If the tiles come out too big, the `--drop-densest-as-needed` option will make
|
||||
Tippecanoe try dropping what should be the least visible features at each zoom level.
|
||||
(If it drops too many features, use `-x` to leave out some feature attributes that
|
||||
you didn't really need.)
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
Create a tileset of TIGER roads for Alameda County, to zoom level 13, with a custom layer name and description:
|
||||
|
||||
```sh
|
||||
$ tippecanoe -o alameda.mbtiles -l alameda -n "Alameda County from TIGER" -z13 tl_2014_06001_roads.json
|
||||
```
|
||||
|
||||
Create a tileset of all TIGER roads, at only zoom level 12, but with higher detail than normal,
|
||||
with a custom layer name and description, and leaving out the `LINEARID` and `RTTYP` attributes:
|
||||
|
||||
```
|
||||
$ 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
|
||||
```
|
||||
|
||||
Cookbook
|
||||
--------
|
||||
|
||||
### Linear features (world railroads), visible at all zoom levels
|
||||
|
||||
```
|
||||
curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_railroads.zip
|
||||
unzip ne_10m_railroads.zip
|
||||
ogr2ogr -f GeoJSON ne_10m_railroads.geojson ne_10m_railroads.shp
|
||||
|
||||
tippecanoe -zg -o ne_10m_railroads.mbtiles --drop-densest-as-needed --extend-zooms-if-still-dropping ne_10m_railroads.geojson
|
||||
```
|
||||
|
||||
* `-zg`: Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
|
||||
* `--drop-densest-as-needed`: If the tiles are too big at low zoom levels, drop the least-visible features to allow tiles to be created with those features that remain
|
||||
* `--extend-zooms-if-still-dropping`: If even the tiles at high zoom levels are too big, keep adding zoom levels until one is reached that can represent all the features
|
||||
|
||||
### Discontinuous polygon features (buildings of Rhode Island), visible at all zoom levels
|
||||
|
||||
```
|
||||
curl -L -O https://usbuildingdata.blob.core.windows.net/usbuildings-v1-1/RhodeIsland.zip
|
||||
unzip RhodeIsland.zip
|
||||
|
||||
tippecanoe -zg -o RhodeIsland.mbtiles --drop-densest-as-needed --extend-zooms-if-still-dropping RhodeIsland.geojson
|
||||
```
|
||||
|
||||
* `-zg`: Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
|
||||
* `--drop-densest-as-needed`: If the tiles are too big at low or medium zoom levels, drop the least-visible features to allow tiles to be created with those features that remain
|
||||
* `--extend-zooms-if-still-dropping`: If even the tiles at high zoom levels are too big, keep adding zoom levels until one is reached that can represent all the features
|
||||
|
||||
### Continuous polygon features (states and provinces), visible at all zoom levels
|
||||
|
||||
```
|
||||
curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zip
|
||||
unzip -o ne_10m_admin_1_states_provinces.zip
|
||||
ogr2ogr -f GeoJSON ne_10m_admin_1_states_provinces.geojson ne_10m_admin_1_states_provinces.shp
|
||||
|
||||
tippecanoe -zg -o ne_10m_admin_1_states_provinces.mbtiles --coalesce-densest-as-needed --extend-zooms-if-still-dropping ne_10m_admin_1_states_provinces.geojson
|
||||
```
|
||||
|
||||
* `-zg`: Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
|
||||
* `--coalesce-densest-as-needed`: If the tiles are too big at low or medium zoom levels, merge as many features together as are necessary to allow tiles to be created with those features that are still distinguished
|
||||
* `--extend-zooms-if-still-dropping`: If even the tiles at high zoom levels are too big, keep adding zoom levels until one is reached that can represent all the features
|
||||
|
||||
### Large point dataset (GPS bus locations), for visualization at all zoom levels
|
||||
|
||||
```
|
||||
curl -L -O ftp://avl-data.sfmta.com/avl_data/avl_raw/sfmtaAVLRawData01012013.csv
|
||||
sed 's/PREDICTABLE.*/PREDICTABLE/' sfmtaAVLRawData01012013.csv > sfmta.csv
|
||||
tippecanoe -zg -o sfmta.mbtiles --drop-densest-as-needed --extend-zooms-if-still-dropping sfmta.csv
|
||||
```
|
||||
|
||||
(The `sed` line is to clean the corrupt CSV header, which contains the wrong number of fields.)
|
||||
|
||||
* `-zg`: Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
|
||||
* `--drop-densest-as-needed`: If the tiles are too big at low or medium zoom levels, drop the least-visible features to allow tiles to be created with those features that remain
|
||||
* `--extend-zooms-if-still-dropping`: If even the tiles at high zoom levels are too big, keep adding zoom levels until one is reached that can represent all the features
|
||||
|
||||
### Clustered points (world cities), summing the clustered population, visible at all zoom levels
|
||||
|
||||
```
|
||||
curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_populated_places.zip
|
||||
unzip -o ne_10m_populated_places.zip
|
||||
ogr2ogr -f GeoJSON ne_10m_populated_places.geojson ne_10m_populated_places.shp
|
||||
|
||||
tippecanoe -zg -o ne_10m_populated_places.mbtiles -r1 --cluster-distance=10 --accumulate-attribute=POP_MAX:sum ne_10m_populated_places.geojson
|
||||
```
|
||||
|
||||
* `-zg`: Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
|
||||
* `-r1`: Do not automatically drop a fraction of points at low zoom levels, since clustering will be used instead
|
||||
* `--cluster-distance=10`: Cluster together features that are closer than about 10 pixels from each other
|
||||
* `--accumulate-attribute=POP_MAX:sum`: Sum the `POP_MAX` (population) attribute in features that are clustered together. Other attributes will be arbitrarily taken from the first feature in the cluster.
|
||||
|
||||
### Show countries at low zoom levels but states at higher zoom levels
|
||||
|
||||
```
|
||||
curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_0_countries.zip
|
||||
unzip ne_10m_admin_0_countries.zip
|
||||
ogr2ogr -f GeoJSON ne_10m_admin_0_countries.geojson ne_10m_admin_0_countries.shp
|
||||
|
||||
curl -L -O https://www.naturalearthdata.com/http//www.naturalearthdata.com/download/10m/cultural/ne_10m_admin_1_states_provinces.zip
|
||||
unzip -o ne_10m_admin_1_states_provinces.zip
|
||||
ogr2ogr -f GeoJSON ne_10m_admin_1_states_provinces.geojson ne_10m_admin_1_states_provinces.shp
|
||||
|
||||
tippecanoe -z3 -o countries-z3.mbtiles --coalesce-densest-as-needed ne_10m_admin_0_countries.geojson
|
||||
tippecanoe -zg -Z4 -o states-Z4.mbtiles --coalesce-densest-as-needed --extend-zooms-if-still-dropping ne_10m_admin_1_states_provinces.geojson
|
||||
tile-join -o states-countries.mbtiles countries-z3.mbtiles states-Z4.mbtiles
|
||||
```
|
||||
|
||||
Countries:
|
||||
|
||||
* `-z3`: Only generate zoom levels 0 through 3
|
||||
* `--coalesce-densest-as-needed`: If the tiles are too big at low or medium zoom levels, merge as many features together as are necessary to allow tiles to be created with those features that are still distinguished
|
||||
|
||||
States and Provinces:
|
||||
|
||||
* `-Z4`: Only generate zoom levels 4 and beyond
|
||||
* `-zg`: Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
|
||||
* `--coalesce-densest-as-needed`: If the tiles are too big at low or medium zoom levels, merge as many features together as are necessary to allow tiles to be created with those features that are still distinguished
|
||||
* `--extend-zooms-if-still-dropping`: If even the tiles at high zoom levels are too big, keep adding zoom levels until one is reached that can represent all the features
|
||||
|
||||
### Represent multiple sources (Illinois and Indiana counties) as separate layers
|
||||
|
||||
```
|
||||
curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/COUNTY/2010/tl_2010_17_county10.zip
|
||||
unzip tl_2010_17_county10.zip
|
||||
ogr2ogr -f GeoJSON tl_2010_17_county10.geojson tl_2010_17_county10.shp
|
||||
|
||||
curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/COUNTY/2010/tl_2010_18_county10.zip
|
||||
unzip tl_2010_18_county10.zip
|
||||
ogr2ogr -f GeoJSON tl_2010_18_county10.geojson tl_2010_18_county10.shp
|
||||
|
||||
tippecanoe -zg -o counties-separate.mbtiles --coalesce-densest-as-needed --extend-zooms-if-still-dropping tl_2010_17_county10.geojson tl_2010_18_county10.geojson
|
||||
```
|
||||
|
||||
* `-zg`: Automatically choose a maxzoom that should be sufficient to clearly distinguish the features and the detail within each feature
|
||||
* `--coalesce-densest-as-needed`: If the tiles are too big at low or medium zoom levels, merge as many features together as are necessary to allow tiles to be created with those features that are still distinguished
|
||||
* `--extend-zooms-if-still-dropping`: If even the tiles at high zoom levels are too big, keep adding zoom levels until one is reached that can represent all the features
|
||||
|
||||
### Merge multiple sources (Illinois and Indiana counties) into the same layer
|
||||
|
||||
```
|
||||
curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/COUNTY/2010/tl_2010_17_county10.zip
|
||||
unzip tl_2010_17_county10.zip
|
||||
ogr2ogr -f GeoJSON tl_2010_17_county10.geojson tl_2010_17_county10.shp
|
||||
|
||||
curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/COUNTY/2010/tl_2010_18_county10.zip
|
||||
unzip tl_2010_18_county10.zip
|
||||
ogr2ogr -f GeoJSON tl_2010_18_county10.geojson tl_2010_18_county10.shp
|
||||
|
||||
tippecanoe -zg -o counties-merged.mbtiles -l counties --coalesce-densest-as-needed --extend-zooms-if-still-dropping tl_2010_17_county10.geojson tl_2010_18_county10.geojson
|
||||
```
|
||||
|
||||
As above, but
|
||||
|
||||
* `-l counties`: Specify the layer name instead of letting it be derived from the source file names
|
||||
|
||||
### Selectively remove and replace features (Census tracts) to update a tileset
|
||||
|
||||
```
|
||||
# Retrieve and tile California 2000 Census tracts
|
||||
curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/TRACT/2000/tl_2010_06_tract00.zip
|
||||
unzip tl_2010_06_tract00.zip
|
||||
ogr2ogr -f GeoJSON tl_2010_06_tract00.shp.json tl_2010_06_tract00.shp
|
||||
tippecanoe -z11 -o tracts.mbtiles -l tracts tl_2010_06_tract00.shp.json
|
||||
|
||||
# Create a copy of the tileset, minus Alameda County (FIPS code 001)
|
||||
tile-join -j '{"*":["none",["==","COUNTYFP00","001"]]}' -f -o tracts-filtered.mbtiles tracts.mbtiles
|
||||
|
||||
# Retrieve and tile Alameda County Census tracts for 2010
|
||||
curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/TRACT/2010/tl_2010_06001_tract10.zip
|
||||
unzip tl_2010_06001_tract10.zip
|
||||
ogr2ogr -f GeoJSON tl_2010_06001_tract10.shp.json tl_2010_06001_tract10.shp
|
||||
tippecanoe -z11 -o tracts-added.mbtiles -l tracts tl_2010_06001_tract10.shp.json
|
||||
|
||||
# Merge the filtered tileset and the tileset of new tracts into a final tileset
|
||||
tile-join -o tracts-final.mbtiles tracts-filtered.mbtiles tracts-added.mbtiles
|
||||
```
|
||||
|
||||
The `-z11` option explicitly specifies the maxzoom, to make sure both the old and new tilesets have the same zoom range.
|
||||
|
||||
The `-j` option to `tile-join` specifies a filter, so that only the desired features will be copied to the new tileset.
|
||||
This filter excludes (using `none`) any features whose FIPS code (`COUNTYFP00`) is the code for Alameda County (`001`).
|
||||
|
||||
Options
|
||||
-------
|
||||
|
||||
### Naming
|
||||
There are a lot of options. A lot of the time you won't want to use any of them
|
||||
other than `-o` _output_`.mbtiles` to name the output file, and probably `-f` to
|
||||
delete the file that already exists with that name.
|
||||
|
||||
* -l _name_ or --layer=_name_: Layer name (default "file" if source is file.json or output is file.mbtiles). If there are multiple input files
|
||||
specified, the files are all merged into the single named layer, even if they try to specify individual names with -L.
|
||||
* -L _name_:_file.json_ or --named-layer=_name_:_file.json_: Specify layer names for individual files. If your shell supports it, you can use a subshell redirect like -L _name_:<(cat dir/*.json) to specify a layer name for the output of streamed input.
|
||||
* -n _name_ or --name=_name_: Human-readable name for the tileset (default file.json)
|
||||
* -A _text_ or --attribution=_text_: Attribution (HTML) to be shown with maps that use data from this tileset.
|
||||
If you aren't sure what the right maxzoom is for your data, `-zg` will guess one for you
|
||||
based on the density of features.
|
||||
|
||||
### File control
|
||||
Tippecanoe will normally drop a fraction of point features at zooms below the maxzoom,
|
||||
to keep the low-zoom tiles from getting too big. If you have a smaller data set where
|
||||
all the points would fit without dropping any of them, use `-r1` to keep them all.
|
||||
If you do want point dropping, but you still want the tiles to be denser than `-zg`
|
||||
thinks they should be, use `-B` to set a basezoom lower than the maxzoom.
|
||||
|
||||
* -o _file_.mbtiles or --output=_file_.mbtiles: Name the output file.
|
||||
* -f or --force: Delete the mbtiles file if it already exists instead of giving an error
|
||||
* -F or --allow-existing: Proceed (without deleting existing data) if the metadata or tiles table already exists
|
||||
or if metadata fields can't be set
|
||||
* -t _directory_ or --temporary-directory=_directory_: Put the temporary files in _directory_.
|
||||
If you don't specify, it will use `/tmp`.
|
||||
* -P or --read-parallel: Use multiple threads to read different parts of each input file at once.
|
||||
If some of your tiles are coming out too big in spite of the settings above, you will
|
||||
often want to use `--drop-densest-as-needed` to drop whatever fraction of the features
|
||||
is necessary at each zoom level to make that zoom level's tiles work.
|
||||
|
||||
If your features have a lot of attributes, use `-y` to keep only the ones you really need.
|
||||
|
||||
If your input is formatted as newline-delimited GeoJSON, use `-P` to make input parsing a lot faster.
|
||||
|
||||
### Output tileset
|
||||
|
||||
* `-o` _file_`.mbtiles` or `--output=`_file_`.mbtiles`: Name the output file.
|
||||
* `-e` _directory_ or `--output-to-directory`=_directory_: Write tiles to the specified *directory* instead of to an mbtiles file.
|
||||
* `-f` or `--force`: Delete the mbtiles file if it already exists instead of giving an error
|
||||
* `-F` or `--allow-existing`: Proceed (without deleting existing data) if the metadata or tiles table already exists
|
||||
or if metadata fields can't be set. You probably don't want to use this.
|
||||
|
||||
### Tileset description and attribution
|
||||
|
||||
* `-n` _name_ or `--name=`_name_: Human-readable name for the tileset (default file.json)
|
||||
* `-A` _text_ or `--attribution=`_text_: Attribution (HTML) to be shown with maps that use data from this tileset.
|
||||
* `-N` _description_ or `--description=`_description_: Description for the tileset (default file.mbtiles)
|
||||
|
||||
### Input files and layer names
|
||||
|
||||
* _name_`.json` or _name_`.geojson`: Read the named GeoJSON input file into a layer called _name_.
|
||||
* _name_`.json.gz` or _name_`.geojson.gz`: Read the named gzipped GeoJSON input file into a layer called _name_.
|
||||
* _name_`.geobuf`: Read the named Geobuf input file into a layer called _name_.
|
||||
* _name_`.csv`: Read the named CSV input file into a layer called _name_.
|
||||
* `-l` _name_ or `--layer=`_name_: Use the specified layer name instead of deriving a name from the input filename or output tileset. If there are multiple input files
|
||||
specified, the files are all merged into the single named layer, even if they try to specify individual names with `-L`.
|
||||
* `-L` _name_`:`_file.json_ or `--named-layer=`_name_`:`_file.json_: Specify layer names for individual files. If your shell supports it, you can use a subshell redirect like `-L` _name_`:<(cat dir/*.json)` to specify a layer name for the output of streamed input.
|
||||
* `-L{`_layer-json_`}` or `--named-layer={`_layer-json_`}`: Specify an input file and layer options by a JSON object. The JSON object must contain a `"file"` key to specify the filename to read from. (If the `"file"` key is an empty string, it means to read from the standard input stream.) It may also contain a `"layer"` field to specify the name of the layer, and/or a `"description"` field to specify the layer's description in the tileset metadata, and/or a `"format"` field to specify `csv` or `geobuf` file format if it is not obvious from the `name`. Example:
|
||||
|
||||
```
|
||||
tippecanoe -z5 -o world.mbtiles -L'{"file":"ne_10m_admin_0_countries.json", "layer":"countries", "description":"Natural Earth countries"}'
|
||||
```
|
||||
|
||||
CSV input files currently support only Point geometries, from columns named `latitude`, `longitude`, `lat`, `lon`, `long`, `lng`, `x`, or `y`.
|
||||
|
||||
### Parallel processing of input
|
||||
|
||||
* `-P` or `--read-parallel`: Use multiple threads to read different parts of each GeoJSON input file at once.
|
||||
This will only work if the input is line-delimited JSON with each Feature on its
|
||||
own line, because it knows nothing of the top-level structure around the Features. Spurious "EOF" error
|
||||
messages may result otherwise.
|
||||
Performance will be better if the input is a named file that can be mapped into memory
|
||||
rather than a stream that can only be read sequentially.
|
||||
|
||||
### Zoom levels and resolution
|
||||
If the input file begins with the [RFC 8142](https://tools.ietf.org/html/rfc8142) record separator,
|
||||
parallel processing of input will be invoked automatically, splitting at record separators rather
|
||||
than at all newlines.
|
||||
|
||||
* -z _zoom_ or --maximum-zoom=_zoom_: Maxzoom: the highest zoom level for which tiles are generated (default 14)
|
||||
* -Z _zoom_ or --minimum-zoom=_zoom_: Minzoom: the lowest zoom level for which tiles are generated (default 0)
|
||||
* -B _zoom_ or --base-zoom=_zoom_: Base zoom, the level at and above which all points are included in the tiles (default maxzoom).
|
||||
If you use -Bg, it will guess a zoom level that will keep at most 50,000 features in the densest tile.
|
||||
You can also specify a marker-width with -Bg*width* to allow fewer features in the densest tile to
|
||||
compensate for the larger marker, or -Bf*number* to allow at most *number* features in the densest tile.
|
||||
* -d _detail_ or --full-detail=_detail_: Detail at max zoom level (default 12, for tile resolution of 4096)
|
||||
* -D _detail_ or --low-detail=_detail_: Detail at lower zoom levels (default 12, for tile resolution of 4096)
|
||||
* -m _detail_ or --minimum-detail=_detail_: Minimum detail that it will try if tiles are too big at regular detail (default 7)
|
||||
* -b _pixels_ or --buffer=_pixels_: Buffer size where features are duplicated from adjacent tiles. Units are "screen pixels"--1/256th of the tile width or height. (default 5)
|
||||
* -s _projection_ or --projection=_projection_: Specify the projection of the input data. Currently supported are EPSG:4326 (WGS84, the default) and EPSG:3857 (Web Mercator).
|
||||
* -M _bytes_ or --maximum-tile-bytes=_bytes_: Use the specified number of _bytes_ as the maximum compressed tile size instead of 500K.
|
||||
Parallel processing will also be automatic if the input file is in Geobuf format.
|
||||
|
||||
### Projection of input
|
||||
|
||||
* `-s` _projection_ or `--projection=`_projection_: Specify the projection of the input data. Currently supported are `EPSG:4326` (WGS84, the default) and `EPSG:3857` (Web Mercator). In general you should use WGS84 for your input files if at all possible.
|
||||
|
||||
### Zoom levels
|
||||
|
||||
* `-z` _zoom_ or `--maximum-zoom=`_zoom_: Maxzoom: the highest zoom level for which tiles are generated (default 14)
|
||||
* `-zg` or `--maximum-zoom=g`: Guess what is probably a reasonable maxzoom based on the spacing of features.
|
||||
* `-Z` _zoom_ or `--minimum-zoom=`_zoom_: Minzoom: the lowest zoom level for which tiles are generated (default 0)
|
||||
* `-ae` or `--extend-zooms-if-still-dropping`: Increase the maxzoom if features are still being dropped at that zoom level.
|
||||
The detail and simplification options that ordinarily apply only to the maximum zoom level will apply both to the originally
|
||||
specified maximum zoom and to any levels added beyond that.
|
||||
* `-R` _zoom_`/`_x_`/`_y_ or `--one-tile=`_zoom_`/`_x_`/`_y_: Set the minzoom and maxzoom to _zoom_ and produce only
|
||||
the single specified tile at that zoom level.
|
||||
|
||||
If you know the precision to which you want your data to be represented,
|
||||
or the map scale of a corresponding printed map,
|
||||
this table shows the approximate precision and scale corresponding to various
|
||||
`-z` options if you use the default `-d` detail of 12:
|
||||
|
||||
zoom level | precision (ft) | precision (m) | map scale
|
||||
---------- | -------------- | ------------- | ---------
|
||||
`-z0` | 32000 ft | 10000 m | 1:320,000,000
|
||||
`-z1` | 16000 ft | 5000 m | 1:160,000,000
|
||||
`-z2` | 8000 ft | 2500 m | 1:80,000,000
|
||||
`-z3` | 4000 ft | 1250 m | 1:40,000,000
|
||||
`-z4` | 2000 ft | 600 m | 1:20,000,000
|
||||
`-z5` | 1000 ft | 300 m | 1:10,000,000
|
||||
`-z6` | 500 ft | 150 m | 1:5,000,000
|
||||
`-z7` | 250 ft | 80 m | 1:2,500,000
|
||||
`-z8` | 125 ft | 40 m | 1:1,250,000
|
||||
`-z9` | 64 ft | 20 m | 1:640,000
|
||||
`-z10` | 32 ft | 10 m | 1:320,000
|
||||
`-z11` | 16 ft | 5 m | 1:160,000
|
||||
`-z12` | 8 ft | 2 m | 1:80,000
|
||||
`-z13` | 4 ft | 1 m | 1:40,000
|
||||
`-z14` | 2 ft | 0.5 m | 1:20,000
|
||||
`-z15` | 1 ft | 0.25 m | 1:10,000
|
||||
`-z16` | 6 in | 15 cm | 1:5000
|
||||
`-z17` | 3 in | 8 cm | 1:2500
|
||||
`-z18` | 1.5 in | 4 cm | 1:1250
|
||||
`-z19` | 0.8 in | 2 cm | 1:600
|
||||
`-z20` | 0.4 in | 1 cm | 1:300
|
||||
`-z21` | 0.2 in | 0.5 cm | 1:150
|
||||
`-z22` | 0.1 in | 0.25 cm | 1:75
|
||||
|
||||
### Tile resolution
|
||||
|
||||
* `-d` _detail_ or `--full-detail=`_detail_: Detail at max zoom level (default 12, for tile resolution of 2^12=4096)
|
||||
* `-D` _detail_ or `--low-detail=`_detail_: Detail at lower zoom levels (default 12, for tile resolution of 2^12=4096)
|
||||
* `-m` _detail_ or `--minimum-detail=`_detail_: Minimum detail that it will try if tiles are too big at regular detail (default 7)
|
||||
|
||||
All internal math is done in terms of a 32-bit tile coordinate system, so 1/(2^32) of the size of Earth,
|
||||
or about 1cm, is the smallest distinguishable distance. If _maxzoom_ + _detail_ > 32, no additional
|
||||
resolution is obtained than by using a smaller _maxzoom_ or _detail_.
|
||||
|
||||
### Properties
|
||||
### Filtering feature attributes
|
||||
|
||||
* -x _name_ or --exclude=_name_: Exclude the named properties from all features
|
||||
* -y _name_ or --include=_name_: Include the named properties in all features, excluding all those not explicitly named
|
||||
* -X or --exclude-all: Exclude all properties and encode only geometries
|
||||
* `-x` _name_ or `--exclude=`_name_: Exclude the named attributes from all features. You can specify multiple `-x` options to exclude several attributes. (Don't comma-separate names within a single `-x`.)
|
||||
* `-y` _name_ or `--include=`_name_: Include the named attributes in all features, excluding all those not explicitly named. You can specify multiple `-y` options to explicitly include several attributes. (Don't comma-separate names within a single `-y`.)
|
||||
* `-X` or `--exclude-all`: Exclude all attributes and encode only geometries
|
||||
|
||||
### Point simplification
|
||||
### Modifying feature attributes
|
||||
|
||||
* -r _rate_ or --drop-rate=_rate_: Rate at which dots are dropped at zoom levels below basezoom (default 2.5).
|
||||
If you use -rg, it will guess a drop rate that will keep at most 50,000 features in the densest tile.
|
||||
You can also specify a marker-width with -rg*width* to allow fewer features in the densest tile to
|
||||
compensate for the larger marker, or -rf*number* to allow at most *number* features in the densest tile.
|
||||
* -g _gamma_ or --gamma=_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.
|
||||
* `-T`_attribute_`:`_type_ or `--attribute-type=`_attribute_`:`_type_: Coerce the named feature _attribute_ to be of the specified _type_.
|
||||
The _type_ may be `string`, `float`, `int`, or `bool`.
|
||||
If the type is `bool`, then original attributes of `0` (or, if numeric, `0.0`, etc.), `false`, `null`, or the empty string become `false`, and otherwise become `true`.
|
||||
If the type is `float` or `int` and the original attribute was non-numeric, it becomes `0`.
|
||||
If the type is `int` and the original attribute was floating-point, it is rounded to the nearest integer.
|
||||
* `-Y`_attribute_`:`_description_ or `--attribute-description=`_attribute_`:`_description_: Set the `description` for the specified attribute in the tileset metadata to _description_ instead of the usual `String`, `Number`, or `Boolean`.
|
||||
* `-E`_attribute_`:`_operation_ or `--accumulate-attribute=`_attribute_`:`_operation_: Preserve the named _attribute_ from features
|
||||
that are dropped, coalesced-as-needed, or clustered. The _operation_ may be
|
||||
`sum`, `product`, `mean`, `max`, `min`, `concat`, or `comma`
|
||||
to specify how the named _attribute_ is accumulated onto the attribute of the same name in a feature that does survive.
|
||||
* `-pe` or `--empty-csv-columns-are-null`: Treat empty CSV columns as nulls rather than as empty strings.
|
||||
* `-aI` or `--convert-stringified-ids-to-numbers`: If a feature ID is the string representation of a number, convert it to a plain number to use as the feature ID.
|
||||
* `--use-attribute-for-id=`*name*: Use the attribute with the specified *name* as if it were specified as the feature ID. (If this attribute is a stringified number, you must also use `-aI` to convert it to a number.)
|
||||
|
||||
### Filtering features by attributes
|
||||
|
||||
* `-j` *filter* or `--feature-filter`=*filter*: Check features against a per-layer filter (as defined in the [Mapbox GL Style Specification](https://docs.mapbox.com/mapbox-gl-js/style-spec/#other-filter)) and only include those that match. Any features in layers that have no filter specified will be passed through. Filters for the layer `"*"` apply to all layers. The special variable `$zoom` refers to the current zoom level.
|
||||
* `-J` *filter-file* or `--feature-filter-file`=*filter-file*: Like `-j`, but read the filter from a file.
|
||||
|
||||
Example: to find the Natural Earth countries with low `scalerank` but high `LABELRANK`:
|
||||
|
||||
```
|
||||
tippecanoe -z5 -o filtered.mbtiles -j '{ "ne_10m_admin_0_countries": [ "all", [ "<", "scalerank", 3 ], [ ">", "LABELRANK", 5 ] ] }' ne_10m_admin_0_countries.geojson
|
||||
```
|
||||
|
||||
Example: to retain only major TIGER roads at low zoom levels:
|
||||
|
||||
```
|
||||
tippecanoe -o roads.mbtiles -j '{ "*": [ "any", [ ">=", "$zoom", 11 ], [ "in", "MTFCC", "S1100", "S1200" ] ] }' tl_2015_06001_roads.json
|
||||
```
|
||||
|
||||
Tippecanoe also accepts expressions of the form `[ "attribute-filter", name, expression ]`, to filter individual feature attributes
|
||||
instead of entire features. For example, you can exclude the road names at low zoom levels by doing
|
||||
|
||||
```
|
||||
tippecanoe -o roads.mbtiles -j '{ "*": [ "attribute-filter", "FULLNAME", [ ">=", "$zoom", 9 ] ] }' tl_2015_06001_roads.json
|
||||
```
|
||||
|
||||
An `attribute-filter` expression itself is always considered to evaluate to `true` (in other words, to retain the feature instead
|
||||
of dropping it). If you want to use multiple `attribute-filter` expressions, or to use other expressions to remove features from
|
||||
the same layer, enclose them in an `all` expression so they will all be evaluated.
|
||||
|
||||
### Dropping a fixed fraction of features by zoom level
|
||||
|
||||
* `-r` _rate_ or `--drop-rate=`_rate_: Rate at which dots are dropped at zoom levels below basezoom (default 2.5).
|
||||
If you use `-rg`, it will guess a drop rate that will keep at most 50,000 features in the densest tile.
|
||||
You can also specify a marker-width with `-rg`*width* to allow fewer features in the densest tile to
|
||||
compensate for the larger marker, or `-rf`*number* to allow at most *number* features in the densest tile.
|
||||
* `-B` _zoom_ or `--base-zoom=`_zoom_: Base zoom, the level at and above which all points are included in the tiles (default maxzoom).
|
||||
If you use `-Bg`, it will guess a zoom level that will keep at most 50,000 features in the densest tile.
|
||||
You can also specify a marker-width with `-Bg`*width* to allow fewer features in the densest tile to
|
||||
compensate for the larger marker, or `-Bf`*number* to allow at most *number* features in the densest tile.
|
||||
* `-al` or `--drop-lines`: Let "dot" dropping at lower zooms apply to lines too
|
||||
* `-ap` or `--drop-polygons`: Let "dot" dropping at lower zooms apply to polygons too
|
||||
* `-K` _distance_ or `--cluster-distance=`_distance_: Cluster points (as with `--cluster-densest-as-needed`, but without the experimental discovery process) that are approximately within _distance_ of each other. The units are tile coordinates within a nominally 256-pixel tile, so the maximum value of 255 allows only one feature per tile. Values around 10 are probably appropriate for typical marker sizes. See `--cluster-densest-as-needed` below for behavior.
|
||||
|
||||
### Dropping a fraction of features to keep under tile size limits
|
||||
|
||||
* `-as` or `--drop-densest-as-needed`: If a tile is too large, try to reduce it to under 500K by increasing the minimum spacing between features. The discovered spacing applies to the entire zoom level.
|
||||
* `-ad` or `--drop-fraction-as-needed`: Dynamically drop some fraction of features from each zoom level to keep large tiles under the 500K size limit. (This is like `-pd` but applies to the entire zoom level, not to each tile.)
|
||||
* `-an` or `--drop-smallest-as-needed`: Dynamically drop the smallest features (physically smallest: the shortest lines or the smallest polygons) from each zoom level to keep large tiles under the 500K size limit. This option will not work for point features.
|
||||
* `-aN` or `--coalesce-smallest-as-needed`: Dynamically combine the smallest features (physically smallest: the shortest lines or the smallest polygons) from each zoom level into other nearby features to keep large tiles under the 500K size limit. This option will not work for point features, and will probably not help very much with LineStrings. It is mostly intended for polygons, to maintain the full original area covered by polygons while still reducing the feature count somehow. The attributes of the small polygons are *not* preserved into the combined features (except through `--accumulate-attribute`), only their geometry. Furthermore, the polygons to which nested polygons are coalesced may not necessarily be the immediately enclosing features.
|
||||
* `-aD` or `--coalesce-densest-as-needed`: Dynamically combine the densest features from each zoom level into other nearby features to keep large tiles under the 500K size limit. (Again, mostly useful for polygons.)
|
||||
* `-aS` or `--coalesce-fraction-as-needed`: Dynamically combine a fraction of features from each zoom level into other nearby features to keep large tiles under the 500K size limit. (Again, mostly useful for polygons.)
|
||||
* `-pd` or `--force-feature-limit`: 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. (This is like `-ad` but applies to each tile individually, not to the entire zoom level.) You probably don't want to use this.
|
||||
* `-aC` or `--cluster-densest-as-needed`: If a tile is too large, try to reduce its size by increasing the minimum spacing between features, and leaving one placeholder feature from each group. The remaining feature will be given a `"clustered": true` attribute to indicate that it represents a cluster, a `"point_count"` attribute to indicate the number of features that were clustered into it, and a `"sqrt_point_count"` attribute to indicate the relative width of a feature to represent the cluster. If the features being clustered are points, the representative feature will be located at the average of the original points' locations; otherwise, one of the original features will be left as the representative.
|
||||
|
||||
### Dropping tightly overlapping features
|
||||
|
||||
* `-g` _gamma_ or `--gamma=_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.
|
||||
* `-aG` or `--increase-gamma-as-needed`: If a tile is too large, try to reduce it to under 500K by increasing the `-g` gamma. The discovered gamma applies to the entire zoom level. You probably want to use `--drop-densest-as-needed` instead.
|
||||
|
||||
### Line and polygon simplification
|
||||
|
||||
* -S _scale_ or --simplification=_scale_: Multiply the tolerance for line and polygon simplification by _scale_. The standard tolerance tries to keep
|
||||
* `-S` _scale_ or `--simplification=`_scale_: Multiply the tolerance for line and polygon simplification by _scale_. The standard tolerance tries to keep
|
||||
the line or polygon within one tile unit of its proper location. You can probably go up to about 10 without too much visible difference.
|
||||
* `-ps` or `--no-line-simplification`: Don't simplify lines and polygons
|
||||
* `-pS` or `--simplify-only-low-zooms`: Don't simplify lines and polygons at maxzoom (but do simplify at lower zooms)
|
||||
* `-pn` or `--no-simplification-of-shared-nodes`: Don't simplify away nodes that appear in more than one feature or are used multiple times within the same feature, so that the intersection node will not be lost from intersecting roads. (This will not be effective if you also use `--coalesce` or `--detect-shared-borders`.)
|
||||
* `-pt` or `--no-tiny-polygon-reduction`: Don't combine the area of very small polygons into small squares that represent their combined area.
|
||||
|
||||
### Doing more
|
||||
### Attempts to improve shared polygon boundaries
|
||||
|
||||
* -ac or --coalesce: Coalesce adjacent line and polygon features that have the same properties.
|
||||
Note that when overlapping polygons are coalesced, the overlapping region is treated as a hole,
|
||||
which may not be what you want.
|
||||
* -ar or --reverse: Try reversing the directions of lines to make them coalesce and compress better
|
||||
* -ao or --reorder: Reorder features to put ones with the same properties in sequence, to try to get them to coalesce
|
||||
* -al or --drop-lines: Let "dot" dropping at lower zooms apply to lines too
|
||||
* -ap or --drop-polygons: Let "dot" dropping at lower zooms apply to polygons too
|
||||
* -ag or --calculate-feature-density: Add a new attribute, `tippecanoe_feature_density`, to each feature, to record how densely features are spaced in that area of the tile. You can use this attribute in the style to produce a glowing effect where points are densely packed. It can range from 0 in the sparsest areas to 255 in the densest.
|
||||
* -ab or --detect-shared-borders: In the manner of [TopoJSON](https://github.com/mbostock/topojson/wiki/Introduction), detect borders that are shared between multiple polygons and simplify them identically in each polygon. This takes more time and memory than considering each polygon individually.
|
||||
* -aG or --increase-gamma-as-needed: If a tile is too large, try to reduce it to under 500K by increasing the `-g` gamma. The discovered gamma applies to the entire zoom level.
|
||||
* -as or --drop-densest-as-needed: If a tile is too large, try to reduce it to under 500K by increasing the minimum spacing between features. The discovered spacing applies to the entire zoom level.
|
||||
* -ad or --drop-fraction-as-needed: Dynamically drop some fraction of features from each zoom level to keep large tiles under the 500K size limit. (This is like `-pd` but applies to the entire zoom level, not to each tile.)
|
||||
* -an or --drop-smallest-as-needed: Dynamically drop the smallest features (physically smallest: the shortest lines or the smallest polygons) from each zoom level to keep large tiles under the 500K size limit. This option will not work for point features.
|
||||
* -aL or --grid-low-zooms: At all zoom levels below _maxzoom_, snap all lines and polygons to a stairstep grid instead of allowing diagonals. You will also want to specify a tile resolution, probably `-D8`. This option provides a way to display continuous parcel, gridded, or binned data at low zooms without overwhelming the tiles with tiny polygons, since features will either get stretched out to the grid unit or lost entirely, depending on how they happened to be aligned in the original data.
|
||||
* `-ab` or `--detect-shared-borders`: In the manner of [TopoJSON](https://github.com/mbostock/topojson/wiki/Introduction), detect borders that are shared between multiple polygons and simplify them identically in each polygon. This takes more time and memory than considering each polygon individually.
|
||||
* `-aL` or `--grid-low-zooms`: At all zoom levels below _maxzoom_, snap all lines and polygons to a stairstep grid instead of allowing diagonals. You will also want to specify a tile resolution, probably `-D8`. This option provides a way to display continuous parcel, gridded, or binned data at low zooms without overwhelming the tiles with tiny polygons, since features will either get stretched out to the grid unit or lost entirely, depending on how they happened to be aligned in the original data. You probably don't want to use this.
|
||||
|
||||
### Doing less
|
||||
### Controlling clipping to tile boundaries
|
||||
|
||||
* -ps or --no-line-simplification: Don't simplify lines
|
||||
* -pS or --simplify-only-low-zooms: Don't simplify lines at maxzoom (but do simplify at lower zooms)
|
||||
* -pf or --no-feature-limit: Don't limit tiles to 200,000 features
|
||||
* -pk or --no-tile-size-limit: Don't limit tiles to 500K bytes
|
||||
* -pd or --force-feature-limit: 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. (This is like `-ad` but applies to each tile individually, not to the entire zoom level.)
|
||||
* -pi or --preserve-input-order: 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).
|
||||
* -pp or --no-polygon-splitting: Don't split complex polygons (over 700 vertices after simplification) into multiple features.
|
||||
* -pc or --no-clipping: Don't clip features to the size of the tile. If a feature overlaps the tile's bounds or buffer at all, it is included completely. Be careful: this can produce very large tilesets, especially with large polygons.
|
||||
* -pD or --no-duplication: As with --no-clipping, each feature is included intact instead of cut to tile boundaries. In addition, it is included only in a single tile per zoom level rather than potentially in multiple copies. Clients of the tileset must check adjacent tiles (possibly some distance away) to ensure they have all features.
|
||||
* -pt or --no-tiny-polygon-reduction: Don't combine the area of very small polygons into small squares that represent their combined area.
|
||||
* -q or --quiet: Work quietly instead of reporting progress
|
||||
* `-b` _pixels_ or `--buffer=`_pixels_: Buffer size where features are duplicated from adjacent tiles. Units are "screen pixels"—1/256th of the tile width or height. (default 5)
|
||||
* `-pc` or `--no-clipping`: Don't clip features to the size of the tile. If a feature overlaps the tile's bounds or buffer at all, it is included completely. Be careful: this can produce very large tilesets, especially with large polygons.
|
||||
* `-pD` or `--no-duplication`: As with `--no-clipping`, each feature is included intact instead of cut to tile boundaries. In addition, it is included only in a single tile per zoom level rather than potentially in multiple copies. Clients of the tileset must check adjacent tiles (possibly some distance away) to ensure they have all features.
|
||||
|
||||
Example
|
||||
-------
|
||||
### Reordering features within each tile
|
||||
|
||||
```sh
|
||||
$ tippecanoe -o alameda.mbtiles -l alameda -n "Alameda County from TIGER" -z13 tl_2014_06001_roads.json
|
||||
```
|
||||
* `-pi` or `--preserve-input-order`: 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`).
|
||||
* `-ac` or `--coalesce`: Coalesce consecutive features that have the same attributes. This can be useful if you have lots of small polygons with identical attributes and you would like to merge them together.
|
||||
* `-ao` or `--reorder`: Reorder features to put ones with the same attributes in sequence (instead of ones that are approximately spatially adjacent), to try to get them to coalesce. You probably want to use this if you use `--coalesce`.
|
||||
* `-ar` or `--reverse`: Try reversing the directions of lines to make them coalesce and compress better. You probably don't want to use this.
|
||||
* `-ah` or `--hilbert`: Put features in Hilbert Curve order instead of the usual Z-Order. This improves the odds that spatially adjacent features will be sequentially adjacent, and should improve density calculations and spatial coalescing. It should be the default eventually.
|
||||
|
||||
### Adding calculated attributes
|
||||
|
||||
* `-ag` or `--calculate-feature-density`: Add a new attribute, `tippecanoe_feature_density`, to each feature, to record how densely features are spaced in that area of the tile. You can use this attribute in the style to produce a glowing effect where points are densely packed. It can range from 0 in the sparsest areas to 255 in the densest.
|
||||
* `-ai` or `--generate-ids`: Add an `id` (a feature ID, not an attribute named `id`) to each feature that does not already have one. There is currently no guarantee that the `id` added will be stable between runs or that it will not conflict with manually-assigned feature IDs. Future versions of Tippecanoe may change the mechanism for allocating IDs.
|
||||
|
||||
### Trying to correct bad source geometry
|
||||
|
||||
* `-aw` or `--detect-longitude-wraparound`: Detect when consecutive points within a feature jump to the other side of the world, and try to fix the geometry.
|
||||
* `-pw` or `--use-source-polygon-winding`: Instead of respecting GeoJSON polygon ring order, use the original polygon winding in the source data to distinguish inner (clockwise) and outer (counterclockwise) polygon rings.
|
||||
* `-pW` or `--reverse-source-polygon-winding`: Instead of respecting GeoJSON polygon ring order, use the opposite of the original polygon winding in the source data to distinguish inner (counterclockwise) and outer (clockwise) polygon rings.
|
||||
* `--clip-bounding-box=`*minlon*`,`*minlat*`,`*maxlon*`,`*maxlat*: Clip all features to the specified bounding box.
|
||||
|
||||
### Setting or disabling tile size limits
|
||||
|
||||
* `-M` _bytes_ or `--maximum-tile-bytes=`_bytes_: Use the specified number of _bytes_ as the maximum compressed tile size instead of 500K.
|
||||
* `-O` _features_ or `--maximum-tile-features=`_features_: Use the specified number of _features_ as the maximum in a tile instead of 200,000.
|
||||
* `-pf` or `--no-feature-limit`: Don't limit tiles to 200,000 features
|
||||
* `-pk` or `--no-tile-size-limit`: Don't limit tiles to 500K bytes
|
||||
* `-pC` or `--no-tile-compression`: Don't compress the PBF vector tile data. If you are getting "Unimplemented type 3" error messages from a renderer, it is probably because it expects uncompressed tiles using this option rather than the normal gzip-compressed tiles.
|
||||
* `-pg` or `--no-tile-stats`: Don't generate the `tilestats` row in the tileset metadata. Uploads without [tilestats](https://github.com/mapbox/mapbox-geostats) will take longer to process.
|
||||
* `--tile-stats-attributes-limit=`*count*: Include `tilestats` information about at most *count* attributes instead of the default 1000.
|
||||
* `--tile-stats-sample-values-limit=`*count*: Calculate `tilestats` attribute statistics based on *count* values instead of the default 1000.
|
||||
* `--tile-stats-values-limit=`*count*: Report *count* unique attribute values in `tilestats` instead of the default 100.
|
||||
|
||||
### Temporary storage
|
||||
|
||||
* `-t` _directory_ or `--temporary-directory=`_directory_: Put the temporary files in _directory_.
|
||||
If you don't specify, it will use `/tmp`.
|
||||
|
||||
### Progress indicator
|
||||
|
||||
* `-q` or `--quiet`: Work quietly instead of reporting progress or warning messages
|
||||
* `-Q` or `--no-progress-indicator`: Don't report progress, but still give warnings
|
||||
* `-U` _seconds_ or `--progress-interval=`_seconds_: Don't report progress more often than the specified number of _seconds_.
|
||||
* `-v` or `--version`: Report Tippecanoe's version number
|
||||
|
||||
### Filters
|
||||
|
||||
* `-C` _command_ or `--prefilter=`_command_: Specify a shell filter command to be run at the start of assembling each tile
|
||||
* `-c` _command_ or `--postfilter=`_command_: Specify a shell filter command to be run at the end of assembling each tile
|
||||
|
||||
The pre- and post-filter commands allow you to do optional filtering or transformation on the features of each tile
|
||||
as it is created. They are shell commands, run with the zoom level, X, and Y as the `$1`, `$2`, and `$3` arguments.
|
||||
Future versions of Tippecanoe may add additional arguments for more context.
|
||||
|
||||
The features are provided to the filter
|
||||
as a series of newline-delimited GeoJSON objects on the standard input, and `tippecanoe` expects to read another
|
||||
set of GeoJSON features from the filter's standard output.
|
||||
|
||||
The prefilter receives the features at the highest available resolution, before line simplification,
|
||||
polygon topology repair, gamma calculation, dynamic feature dropping, or other internal processing.
|
||||
The postfilter receives the features at tile resolution, after simplification, cleaning, and dropping.
|
||||
|
||||
The layer name is provided as part of the `tippecanoe` element of the feature and must be passed through
|
||||
to keep the feature in its correct layer. In the case of the prefilter, the `tippecanoe` element may also
|
||||
contain `index`, `sequence`, `extent`, and `dropped`, elements, which must be passed through for internal operations like
|
||||
`--drop-densest-as-needed`, `--drop-smallest-as-needed`, and `--preserve-input-order` to work.
|
||||
|
||||
#### Examples:
|
||||
|
||||
* Make a tileset of the Natural Earth countries to zoom level 5, and also copy the GeoJSON features
|
||||
to files in a `tiles/z/x/y.geojson` directory hierarchy.
|
||||
|
||||
```
|
||||
$ 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
|
||||
tippecanoe -o countries.mbtiles -z5 -C 'mkdir -p tiles/$1/$2; tee tiles/$1/$2/$3.geojson' ne_10m_admin_0_countries.json
|
||||
```
|
||||
|
||||
* Make a tileset of the Natural Earth countries to zoom level 5, but including only those tiles that
|
||||
intersect the [bounding box of Germany](https://www.flickr.com/places/info/23424829).
|
||||
(The `limit-tiles-to-bbox` script is [in the Tippecanoe source directory](filters/limit-tiles-to-bbox).)
|
||||
|
||||
```
|
||||
tippecanoe -o countries.mbtiles -z5 -C './filters/limit-tiles-to-bbox 5.8662 47.2702 15.0421 55.0581 $*' ne_10m_admin_0_countries.json
|
||||
```
|
||||
|
||||
* Make a tileset of TIGER roads in Tippecanoe County, leaving out all but primary and secondary roads (as [classified by TIGER](https://www.census.gov/geo/reference/mtfcc.html)) below zoom level 11.
|
||||
|
||||
```
|
||||
tippecanoe -o roads.mbtiles -c 'if [ $1 -lt 11 ]; then grep "\"MTFCC\": \"S1[12]00\""; else cat; fi' tl_2016_18157_roads.json
|
||||
```
|
||||
|
||||
Environment
|
||||
-----------
|
||||
|
||||
Tippecanoe ordinarily uses as many parallel threads as the operating system claims that CPUs are available.
|
||||
You can override this number by setting the `TIPPECANOE_MAX_THREADS` environmental variable.
|
||||
|
||||
GeoJSON extension
|
||||
-----------------
|
||||
|
||||
@ -198,32 +643,11 @@ the filename or name specified using `--layer`, like this:
|
||||
}
|
||||
```
|
||||
|
||||
Point styling
|
||||
-------------
|
||||
If your source GeoJSON only has `minzoom`, `maxzoom` and/or `layer` within `properties` you can use [ndjson-cli](https://github.com/mbostock/ndjson-cli/blob/master/README.md) to move them into the required `tippecanoe` object by piping the GeoJSON like this:
|
||||
|
||||
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 `-B` or `-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);
|
||||
}'
|
||||
```sh
|
||||
ndjson-map 'd.tippecanoe = { minzoom: d.properties.minzoom, maxzoom: d.properties.maxzoom, layer: d.properties.layer }, delete d.properties.minzoom, delete d.properties.maxzoom, delete d.properties.layer, d'
|
||||
```
|
||||
|
||||
Geometric simplifications
|
||||
-------------------------
|
||||
@ -251,12 +675,9 @@ have their probability diffused, so that some of them will be drawn as a square
|
||||
this minimum size and others will not be drawn at all, preserving the total area that
|
||||
all of them should have had together.
|
||||
|
||||
Any polygons that have over 700 vertices after line simplification will be split into
|
||||
multiple features so they can be rendered efficiently, unless you use -pp to prevent this.
|
||||
|
||||
Features in the same tile that share the same type and attributes are coalesced
|
||||
together into a single geometry if you use `--coalesce`. You are strongly encouraged to use -x to exclude
|
||||
any unnecessary properties to reduce wasted file size.
|
||||
any unnecessary attributes to reduce wasted file size.
|
||||
|
||||
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.
|
||||
@ -269,7 +690,7 @@ uses md2man (`gem install md2man`).
|
||||
|
||||
Linux:
|
||||
|
||||
sudo apt-get install libsqlite3-dev zlib1g-dev
|
||||
sudo apt-get install build-essential libsqlite3-dev zlib1g-dev
|
||||
|
||||
Then build:
|
||||
|
||||
@ -279,6 +700,35 @@ and perhaps
|
||||
|
||||
make install
|
||||
|
||||
Tippecanoe now requires features from the 2011 C++ standard. If your compiler is older than
|
||||
that, you will need to install a newer one. On MacOS, updating to the lastest XCode should
|
||||
get you a new enough version of `clang++`. On Linux, you should be able to upgrade `g++` with
|
||||
|
||||
```
|
||||
sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install -y g++-5
|
||||
export CXX=g++-5
|
||||
```
|
||||
|
||||
Docker Image
|
||||
------------
|
||||
|
||||
A tippecanoe Docker image can be built from source and executed as a task to
|
||||
automatically install dependencies and allow tippecanoe to run on any system
|
||||
supported by Docker.
|
||||
|
||||
```docker
|
||||
$ docker build -t tippecanoe:latest .
|
||||
$ docker run -it --rm \
|
||||
-v /tiledata:/data \
|
||||
tippecanoe:latest \
|
||||
tippecanoe --output=/data/output.mbtiles /data/example.geojson
|
||||
```
|
||||
|
||||
The commands above will build a Docker image from the source and compile the
|
||||
latest version. The image supports all tippecanoe flags and options.
|
||||
|
||||
Examples
|
||||
------
|
||||
|
||||
@ -292,22 +742,61 @@ The name is [a joking reference](http://en.wikipedia.org/wiki/Tippecanoe_and_Tyl
|
||||
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.
|
||||
Tile-join is a tool for copying and merging vector mbtiles files and for
|
||||
joining new attributes from a CSV file to existing features in them.
|
||||
|
||||
If you specify multiple source mbtiles files, they are all read and their combined
|
||||
contents are written to the new mbtiles output. If they define the same layers or
|
||||
the same tiles, the layers or tiles are merged.
|
||||
It reads the tiles from an
|
||||
existing .mbtiles file or a directory of tiles, matches them against the
|
||||
records of the CSV (if one is specified), and writes out a new tileset.
|
||||
|
||||
If you specify multiple source mbtiles files or source directories of tiles,
|
||||
all the sources are read and their combined contents are written to the new
|
||||
mbtiles output. If they define the same layers or the same tiles, the layers
|
||||
or tiles are merged.
|
||||
|
||||
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.
|
||||
* -pk: Don't skip tiles larger than 500K.
|
||||
### Output tileset
|
||||
|
||||
* `-o` *out.mbtiles* or `--output=`*out.mbtiles*: Write the new tiles to the specified .mbtiles file.
|
||||
* `-e` *directory* or `--output-to-directory=`*directory*: Write the new tiles to the specified directory instead of to an mbtiles file.
|
||||
* `-f` or `--force`: Remove *out.mbtiles* if it already exists.
|
||||
|
||||
### Tileset description and attribution
|
||||
|
||||
* `-A` *attribution* or `--attribution=`*attribution*: Set the attribution string.
|
||||
* `-n` *name* or `--name=`*name*: Set the tileset name.
|
||||
* `-N` *description* or `--description=`*description*: Set the tileset description.
|
||||
|
||||
### Layer filtering and naming
|
||||
|
||||
* `-l` *layer* or `--layer=`*layer*: Include the named layer in the output. You can specify multiple `-l` options to keep multiple layers. If you don't specify, they will all be retained.
|
||||
* `-L` *layer* or `--exclude-layer=`*layer*: Remove the named layer from the output. You can specify multiple `-L` options to remove multiple layers.
|
||||
* `-R`*old*`:`*new* or `--rename-layer=`*old*`:`*new*: Rename the layer named *old* to be named *new* instead. You can specify multiple `-R` options to rename multiple layers. Renaming happens before filtering.
|
||||
|
||||
### Zoom levels
|
||||
|
||||
* `-z` _zoom_ or `--maximum-zoom=`_zoom_: Don't copy tiles from higher zoom levels than the specified zoom
|
||||
* `-Z` _zoom_ or `--minimum-zoom=`_zoom_: Don't copy tiles from lower zoom levels than the specified zoom
|
||||
|
||||
### Merging attributes from a CSV file
|
||||
|
||||
* `-c` *match*`.csv` or `--csv=`*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.
|
||||
|
||||
### Filtering features and feature attributes
|
||||
|
||||
* `-x` *key* or `--exclude=`*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.
|
||||
* `-X` or `--exclude-all`: Remove all attributes from the output.
|
||||
* `-i` or `--if-matched`: Only include features that matched the CSV.
|
||||
* `-j` *filter* or `--feature-filter`=*filter*: Check features against a per-layer filter (as defined in the [Mapbox GL Style Specification](https://docs.mapbox.com/mapbox-gl-js/style-spec/#other-filter)) and only include those that match. Any features in layers that have no filter specified will be passed through. Filters for the layer `"*"` apply to all layers.
|
||||
* `-J` *filter-file* or `--feature-filter-file`=*filter-file*: Like `-j`, but read the filter from a file.
|
||||
* `-pe` or `--empty-csv-columns-are-null`: Treat empty CSV columns as nulls rather than as empty strings.
|
||||
|
||||
### Setting or disabling tile size limits
|
||||
|
||||
* `-pk` or `--no-tile-size-limit`: Don't skip tiles larger than 500K.
|
||||
* `-pC` or `--no-tile-compression`: Don't compress the PBF vector tile data.
|
||||
* `-pg` or `--no-tile-stats`: Don't generate the `tilestats` row in the tileset metadata. Uploads without [tilestats](https://github.com/mapbox/mapbox-geostats) will take longer to process.
|
||||
|
||||
Because tile-join just copies the geometries to the new .mbtiles without processing them
|
||||
(except to rescale the extents if necessary),
|
||||
@ -320,7 +809,7 @@ 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
|
||||
curl -L -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
|
||||
@ -329,7 +818,7 @@ ogr2ogr -f GeoJSON tl_2010_06001_tabblock10.json tl_2010_06001_tabblock10.shp
|
||||
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
|
||||
curl -L -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"
|
||||
@ -384,11 +873,80 @@ or on an individual tile:
|
||||
tippecanoe-decode file.mbtiles zoom x y
|
||||
tippecanoe-decode file.vector.pbf zoom x y
|
||||
|
||||
If you decode an entire file, you get a nested `FeatureCollection` identifying each
|
||||
Unless you use `-c`, the output is a set of nested FeatureCollections identifying each
|
||||
tile and layer separately. Note that the same features generally appear at all zooms,
|
||||
so the output for the file will have many copies of the same features at different
|
||||
resolutions.
|
||||
|
||||
### Options
|
||||
|
||||
* -t _projection_: Specify the projection of the output data. Currently supported are EPSG:4326 (WGS84, the default) and EPSG:3857 (Web Mercator).
|
||||
* `-s` _projection_ or `--projection=`*projection*: Specify the projection of the output data. Currently supported are EPSG:4326 (WGS84, the default) and EPSG:3857 (Web Mercator).
|
||||
* `-z` _maxzoom_ or `--maximum-zoom=`*maxzoom*: Specify the highest zoom level to decode from the tileset
|
||||
* `-Z` _minzoom_ or `--minimum-zoom=`*minzoom*: Specify the lowest zoom level to decode from the tileset
|
||||
* `-l` _layer_ or `--layer=`*layer*: Decode only layers with the specified names. (Multiple `-l` options can be specified.)
|
||||
* `-c` or `--tag-layer-and-zoom`: Include each feature's layer and zoom level as part of its `tippecanoe` object rather than as a FeatureCollection wrapper
|
||||
* `-S` or `--stats`: Just report statistics about each tile's size and the number of features in it, as a JSON structure.
|
||||
* `-f` or `--force`: Decode tiles even if polygon ring order or closure problems are detected
|
||||
|
||||
tippecanoe-json-tool
|
||||
====================
|
||||
|
||||
Extracts GeoJSON features or standalone geometries as line-delimited JSON objects from a larger JSON file,
|
||||
following the same extraction rules that Tippecanoe uses when parsing JSON.
|
||||
|
||||
tippecanoe-json-tool file.json [... file.json]
|
||||
|
||||
Optionally also wraps them in a FeatureCollection or GeometryCollection as appropriate.
|
||||
|
||||
Optionally extracts an attribute from the GeoJSON `properties` for sorting.
|
||||
|
||||
Optionally joins a sorted CSV of new attributes to a sorted GeoJSON file.
|
||||
|
||||
The reason for requiring sorting is so that it is possible to work on CSV and GeoJSON files that are larger
|
||||
than can comfortably fit in memory by streaming through them in parallel, in the same way that the Unix
|
||||
`join` command does. The Unix `sort` command can be used to sort large files to prepare them for joining.
|
||||
|
||||
The sorting interface is weird, and future version of `tippecanoe-json-tool` will replace it with
|
||||
something better.
|
||||
|
||||
### Options
|
||||
|
||||
* `-w` or `--wrap`: Add the FeatureCollection or GeometryCollection wrapper.
|
||||
* `-e` *attribute* or `--extract=`*attribute*: Extract the named attribute as a prefix to each feature.
|
||||
The formatting makes excessive use of `\u` quoting so that it follows JSON string rules but will still
|
||||
be sorted correctly by tools that just do ASCII comparisons.
|
||||
* `-c` *file.csv* or `--csv=`*file.csv*: Join attributes from the named sorted CSV file, using its first column as the join key. Geometries will be passed through even if they do not match the CSV; CSV lines that do not match a geometry will be discarded.
|
||||
* `-pe` or `--empty-csv-columns-are-null`: Treat empty CSV columns as nulls rather than as empty strings.
|
||||
|
||||
### Example
|
||||
|
||||
Join Census LEHD ([Longitudinal Employer-Household Dynamics](https://lehd.ces.census.gov/)) employment data to a file of Census block geography
|
||||
for Tippecanoe County, Indiana.
|
||||
|
||||
Download Census block geometry, and convert to GeoJSON:
|
||||
|
||||
```
|
||||
$ curl -L -O https://www2.census.gov/geo/tiger/TIGER2010/TABBLOCK/2010/tl_2010_18157_tabblock10.zip
|
||||
$ unzip tl_2010_18157_tabblock10.zip
|
||||
$ ogr2ogr -f GeoJSON tl_2010_18157_tabblock10.json tl_2010_18157_tabblock10.shp
|
||||
```
|
||||
|
||||
Download Indiana employment data, and fix name of join key in header
|
||||
|
||||
```
|
||||
$ curl -L -O https://lehd.ces.census.gov/data/lodes/LODES7/in/wac/in_wac_S000_JT00_2015.csv.gz
|
||||
$ gzip -dc in_wac_S000_JT00_2015.csv.gz | sed '1s/w_geocode/GEOID10/' > in_wac_S000_JT00_2015.csv
|
||||
```
|
||||
|
||||
Sort GeoJSON block geometry so it is ordered by block ID. If you don't do this, you will get a
|
||||
"GeoJSON file is out of sort" error.
|
||||
|
||||
```
|
||||
$ tippecanoe-json-tool -e GEOID10 tl_2010_18157_tabblock10.json | LC_ALL=C sort > tl_2010_18157_tabblock10.sort.json
|
||||
```
|
||||
|
||||
Join block geometries to employment attributes:
|
||||
|
||||
```
|
||||
$ tippecanoe-json-tool -c in_wac_S000_JT00_2015.csv tl_2010_18157_tabblock10.sort.json > blocks-wac.json
|
||||
```
|
||||
|
7
codecov.yml
Normal file
7
codecov.yml
Normal file
@ -0,0 +1,7 @@
|
||||
ignore:
|
||||
- "test"
|
||||
- "mapbox"
|
||||
|
||||
coverage:
|
||||
status:
|
||||
patch: off
|
174
csv.cpp
Normal file
174
csv.cpp
Normal file
@ -0,0 +1,174 @@
|
||||
#include "csv.hpp"
|
||||
#include "text.hpp"
|
||||
|
||||
std::vector<std::string> csv_split(const char *s) {
|
||||
std::vector<std::string> ret;
|
||||
|
||||
while (*s && *s != '\n' && *s != '\r') {
|
||||
const char *start = s;
|
||||
int within = 0;
|
||||
|
||||
for (; *s && *s != '\n' && *s != '\r'; s++) {
|
||||
if (*s == '"') {
|
||||
within = !within;
|
||||
}
|
||||
|
||||
if (*s == ',' && !within) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::string v = std::string(start, s - start);
|
||||
ret.push_back(v);
|
||||
|
||||
if (*s == ',') {
|
||||
s++;
|
||||
|
||||
while (*s && isspace(*s)) {
|
||||
s++;
|
||||
}
|
||||
|
||||
if (*s == '\0' || *s == '\r' || *s == '\n') {
|
||||
ret.push_back(std::string(""));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::string csv_dequote(std::string s) {
|
||||
std::string out;
|
||||
for (size_t 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;
|
||||
}
|
||||
|
||||
std::string csv_getline(FILE *f) {
|
||||
std::string out;
|
||||
int c;
|
||||
while ((c = getc(f)) != EOF) {
|
||||
out.push_back(c);
|
||||
if (c == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void readcsv(const 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);
|
||||
}
|
||||
|
||||
std::string s;
|
||||
if ((s = csv_getline(f)).size() > 0) {
|
||||
std::string err = check_utf8(s);
|
||||
if (err != "") {
|
||||
fprintf(stderr, "%s: %s\n", fn, err.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
header = csv_split(s.c_str());
|
||||
|
||||
for (size_t i = 0; i < header.size(); i++) {
|
||||
header[i] = csv_dequote(header[i]);
|
||||
}
|
||||
}
|
||||
while ((s = csv_getline(f)).size() > 0) {
|
||||
std::string err = check_utf8(s);
|
||||
if (err != "") {
|
||||
fprintf(stderr, "%s: %s\n", fn, err.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::vector<std::string> line = csv_split(s.c_str());
|
||||
if (line.size() > 0) {
|
||||
line[0] = csv_dequote(line[0]);
|
||||
}
|
||||
|
||||
for (size_t 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));
|
||||
}
|
||||
}
|
||||
|
||||
if (fclose(f) != 0) {
|
||||
perror("fclose");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
// Follow JSON rules for what looks like a number
|
||||
bool is_number(std::string const &s) {
|
||||
const char *cp = s.c_str();
|
||||
char c = *(cp++);
|
||||
|
||||
if (c == '-' || (c >= '0' && c <= '9')) {
|
||||
if (c == '-') {
|
||||
c = *(cp++);
|
||||
}
|
||||
|
||||
if (c == '0') {
|
||||
;
|
||||
} else if (c >= '1' && c <= '9') {
|
||||
c = *cp;
|
||||
|
||||
while (c >= '0' && c <= '9') {
|
||||
cp++;
|
||||
c = *cp;
|
||||
}
|
||||
}
|
||||
|
||||
if (*cp == '.') {
|
||||
cp++;
|
||||
|
||||
c = *cp;
|
||||
if (c < '0' || c > '9') {
|
||||
return false;
|
||||
}
|
||||
while (c >= '0' && c <= '9') {
|
||||
cp++;
|
||||
c = *cp;
|
||||
}
|
||||
}
|
||||
|
||||
c = *cp;
|
||||
if (c == 'e' || c == 'E') {
|
||||
cp++;
|
||||
|
||||
c = *cp;
|
||||
if (c == '+' || c == '-') {
|
||||
cp++;
|
||||
}
|
||||
|
||||
c = *cp;
|
||||
if (c < '0' || c > '9') {
|
||||
return false;
|
||||
}
|
||||
while (c >= '0' && c <= '9') {
|
||||
cp++;
|
||||
c = *cp;
|
||||
}
|
||||
}
|
||||
|
||||
if (*cp == '\0') {
|
||||
return true;
|
||||
} else {
|
||||
// Something non-numeric at the end
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
16
csv.hpp
Normal file
16
csv.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#ifndef CSV_HPP
|
||||
#define CSV_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
std::vector<std::string> csv_split(const char *s);
|
||||
std::string csv_dequote(std::string s);
|
||||
void readcsv(const char *fn, std::vector<std::string> &header, std::map<std::string, std::vector<std::string>> &mapping);
|
||||
std::string csv_getline(FILE *f);
|
||||
bool is_number(std::string const &s);
|
||||
|
||||
#endif
|
758
decode.cpp
758
decode.cpp
@ -3,339 +3,234 @@
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <sqlite3.h>
|
||||
#include <getopt.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <zlib.h>
|
||||
#include <math.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/mman.h>
|
||||
#include <protozero/pbf_reader.hpp>
|
||||
#include <sys/stat.h>
|
||||
#include "mvt.hpp"
|
||||
#include "projection.hpp"
|
||||
#include "geometry.hpp"
|
||||
#include "write_json.hpp"
|
||||
#include "jsonpull/jsonpull.h"
|
||||
#include "dirtiles.hpp"
|
||||
|
||||
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);
|
||||
int minzoom = 0;
|
||||
int maxzoom = 32;
|
||||
bool force = false;
|
||||
|
||||
void do_stats(mvt_tile &tile, size_t size, bool compressed, int z, unsigned x, unsigned y, json_writer &state) {
|
||||
state.json_write_hash();
|
||||
|
||||
state.json_write_string("zoom");
|
||||
state.json_write_signed(z);
|
||||
|
||||
state.json_write_string("x");
|
||||
state.json_write_unsigned(x);
|
||||
|
||||
state.json_write_string("y");
|
||||
state.json_write_unsigned(y);
|
||||
|
||||
state.json_write_string("bytes");
|
||||
state.json_write_unsigned(size);
|
||||
|
||||
state.json_write_string("compressed");
|
||||
state.json_write_bool(compressed);
|
||||
|
||||
state.json_write_string("layers");
|
||||
state.json_write_hash();
|
||||
|
||||
for (size_t i = 0; i < tile.layers.size(); i++) {
|
||||
state.json_write_string(tile.layers[i].name);
|
||||
|
||||
size_t points = 0, lines = 0, polygons = 0;
|
||||
for (size_t j = 0; j < tile.layers[i].features.size(); j++) {
|
||||
if (tile.layers[i].features[j].type == mvt_point) {
|
||||
points++;
|
||||
} else if (tile.layers[i].features[j].type == mvt_linestring) {
|
||||
lines++;
|
||||
} else if (tile.layers[i].features[j].type == mvt_polygon) {
|
||||
polygons++;
|
||||
}
|
||||
}
|
||||
|
||||
state.json_write_hash();
|
||||
|
||||
state.json_write_string("points");
|
||||
state.json_write_unsigned(points);
|
||||
|
||||
state.json_write_string("lines");
|
||||
state.json_write_unsigned(lines);
|
||||
|
||||
state.json_write_string("polygons");
|
||||
state.json_write_unsigned(polygons);
|
||||
|
||||
state.json_write_string("extent");
|
||||
state.json_write_signed(tile.layers[i].extent);
|
||||
|
||||
state.json_end_hash();
|
||||
}
|
||||
putchar('"');
|
||||
|
||||
state.json_end_hash();
|
||||
state.json_end_hash();
|
||||
|
||||
state.json_write_newline();
|
||||
}
|
||||
|
||||
struct lonlat {
|
||||
int op;
|
||||
double lon;
|
||||
double lat;
|
||||
int x;
|
||||
int y;
|
||||
|
||||
lonlat(int nop, double nlon, double nlat, int nx, int ny) {
|
||||
this->op = nop;
|
||||
this->lon = nlon;
|
||||
this->lat = nlat;
|
||||
this->x = nx;
|
||||
this->y = ny;
|
||||
}
|
||||
};
|
||||
|
||||
void handle(std::string message, int z, unsigned x, unsigned y, int describe) {
|
||||
int within = 0;
|
||||
void handle(std::string message, int z, unsigned x, unsigned y, std::set<std::string> const &to_decode, bool pipeline, bool stats, json_writer &state) {
|
||||
mvt_tile tile;
|
||||
bool was_compressed;
|
||||
|
||||
try {
|
||||
if (!tile.decode(message)) {
|
||||
if (!tile.decode(message, was_compressed)) {
|
||||
fprintf(stderr, "Couldn't parse tile %d/%u/%u\n", z, x, y);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} catch (protozero::unknown_pbf_wire_type_exception e) {
|
||||
} catch (std::exception const &e) {
|
||||
fprintf(stderr, "PBF decoding error in tile %d/%u/%u\n", z, x, y);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
printf("{ \"type\": \"FeatureCollection\"");
|
||||
|
||||
if (describe) {
|
||||
printf(", \"properties\": { \"zoom\": %d, \"x\": %d, \"y\": %d }", z, x, y);
|
||||
|
||||
if (projection != projections) {
|
||||
printf(", \"crs\": { \"type\": \"name\", \"properties\": { \"name\": ");
|
||||
printq(projection->alias);
|
||||
printf(" } }");
|
||||
}
|
||||
if (stats) {
|
||||
do_stats(tile, message.size(), was_compressed, z, x, y, state);
|
||||
return;
|
||||
}
|
||||
|
||||
printf(", \"features\": [\n");
|
||||
if (!pipeline) {
|
||||
state.json_write_hash();
|
||||
|
||||
state.json_write_string("type");
|
||||
state.json_write_string("FeatureCollection");
|
||||
|
||||
if (true) {
|
||||
state.json_write_string("properties");
|
||||
state.json_write_hash();
|
||||
|
||||
state.json_write_string("zoom");
|
||||
state.json_write_signed(z);
|
||||
|
||||
state.json_write_string("x");
|
||||
state.json_write_signed(x);
|
||||
|
||||
state.json_write_string("y");
|
||||
state.json_write_signed(y);
|
||||
|
||||
if (!was_compressed) {
|
||||
state.json_write_string("compressed");
|
||||
state.json_write_bool(false);
|
||||
}
|
||||
|
||||
state.json_end_hash();
|
||||
|
||||
if (projection != projections) {
|
||||
state.json_write_string("crs");
|
||||
state.json_write_hash();
|
||||
|
||||
state.json_write_string("type");
|
||||
state.json_write_string("name");
|
||||
|
||||
state.json_write_string("properties");
|
||||
state.json_write_hash();
|
||||
|
||||
state.json_write_string("name");
|
||||
state.json_write_string(projection->alias);
|
||||
|
||||
state.json_end_hash();
|
||||
state.json_end_hash();
|
||||
}
|
||||
}
|
||||
|
||||
state.json_write_string("features");
|
||||
state.json_write_array();
|
||||
state.json_write_newline();
|
||||
}
|
||||
|
||||
bool first_layer = true;
|
||||
for (size_t l = 0; l < tile.layers.size(); l++) {
|
||||
mvt_layer &layer = tile.layers[l];
|
||||
int extent = layer.extent;
|
||||
|
||||
if (describe) {
|
||||
if (l != 0) {
|
||||
printf(",\n");
|
||||
}
|
||||
|
||||
printf("{ \"type\": \"FeatureCollection\"");
|
||||
printf(", \"properties\": { \"layer\": ");
|
||||
printq(layer.name.c_str());
|
||||
printf(", \"version\": %d, \"extent\": %d", layer.version, layer.extent);
|
||||
printf(" }");
|
||||
printf(", \"features\": [\n");
|
||||
|
||||
within = 0;
|
||||
if (layer.extent <= 0) {
|
||||
fprintf(stderr, "Impossible layer extent %lld in mbtiles\n", layer.extent);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
for (size_t f = 0; f < layer.features.size(); f++) {
|
||||
mvt_feature &feat = layer.features[f];
|
||||
|
||||
if (within) {
|
||||
printf(",\n");
|
||||
}
|
||||
within = 1;
|
||||
|
||||
printf("{ \"type\": \"Feature\"");
|
||||
|
||||
if (feat.has_id) {
|
||||
printf(", \"id\": %llu", feat.id);
|
||||
}
|
||||
|
||||
printf(", \"properties\": { ");
|
||||
|
||||
for (size_t t = 0; t + 1 < feat.tags.size(); t += 2) {
|
||||
if (t != 0) {
|
||||
printf(", ");
|
||||
}
|
||||
|
||||
if (feat.tags[t] >= layer.keys.size()) {
|
||||
fprintf(stderr, "Error: out of bounds feature key\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (feat.tags[t + 1] >= layer.values.size()) {
|
||||
fprintf(stderr, "Error: out of bounds feature value\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
const char *key = layer.keys[feat.tags[t]].c_str();
|
||||
mvt_value const &val = layer.values[feat.tags[t + 1]];
|
||||
|
||||
if (val.type == mvt_string) {
|
||||
printq(key);
|
||||
printf(": ");
|
||||
printq(val.string_value.c_str());
|
||||
} else if (val.type == mvt_int) {
|
||||
printq(key);
|
||||
printf(": %lld", (long long) val.numeric_value.int_value);
|
||||
} else if (val.type == mvt_double) {
|
||||
printq(key);
|
||||
double v = val.numeric_value.double_value;
|
||||
if (v == (long long) v) {
|
||||
printf(": %lld", (long long) v);
|
||||
} else {
|
||||
printf(": %g", v);
|
||||
}
|
||||
} else if (val.type == mvt_float) {
|
||||
printq(key);
|
||||
double v = val.numeric_value.float_value;
|
||||
if (v == (long long) v) {
|
||||
printf(": %lld", (long long) v);
|
||||
} else {
|
||||
printf(": %g", v);
|
||||
}
|
||||
} else if (val.type == mvt_sint) {
|
||||
printq(key);
|
||||
printf(": %lld", (long long) val.numeric_value.sint_value);
|
||||
} else if (val.type == mvt_uint) {
|
||||
printq(key);
|
||||
printf(": %lld", (long long) val.numeric_value.uint_value);
|
||||
} else if (val.type == mvt_bool) {
|
||||
printq(key);
|
||||
printf(": %s", val.numeric_value.bool_value ? "true" : "false");
|
||||
}
|
||||
}
|
||||
|
||||
printf(" }, \"geometry\": { ");
|
||||
|
||||
std::vector<lonlat> ops;
|
||||
|
||||
for (size_t g = 0; g < feat.geometry.size(); g++) {
|
||||
int op = feat.geometry[g].op;
|
||||
long long px = feat.geometry[g].x;
|
||||
long long py = feat.geometry[g].y;
|
||||
|
||||
if (op == VT_MOVETO || op == VT_LINETO) {
|
||||
long long scale = 1LL << (32 - z);
|
||||
long long wx = scale * x + (scale / extent) * px;
|
||||
long long wy = scale * y + (scale / extent) * py;
|
||||
|
||||
double lat, lon;
|
||||
projection->unproject(wx, wy, 32, &lon, &lat);
|
||||
|
||||
ops.push_back(lonlat(op, lon, lat, px, py));
|
||||
} else {
|
||||
ops.push_back(lonlat(op, 0, 0, 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 (size_t 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 (size_t i = 0; i < ops.size(); i++) {
|
||||
if (ops[i].op == VT_MOVETO) {
|
||||
movetos++;
|
||||
}
|
||||
}
|
||||
|
||||
if (movetos < 2) {
|
||||
printf("\"type\": \"LineString\", \"coordinates\": [ ");
|
||||
for (size_t 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 (size_t 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<lonlat> > rings;
|
||||
std::vector<double> areas;
|
||||
|
||||
for (size_t i = 0; i < ops.size(); i++) {
|
||||
if (ops[i].op == VT_MOVETO) {
|
||||
rings.push_back(std::vector<lonlat>());
|
||||
areas.push_back(0);
|
||||
}
|
||||
|
||||
int n = rings.size() - 1;
|
||||
if (n >= 0) {
|
||||
if (ops[i].op == VT_CLOSEPATH) {
|
||||
rings[n].push_back(rings[n][0]);
|
||||
} else {
|
||||
rings[n].push_back(ops[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int outer = 0;
|
||||
|
||||
for (size_t i = 0; i < rings.size(); i++) {
|
||||
long double area = 0;
|
||||
for (size_t k = 0; k < rings[i].size(); k++) {
|
||||
if (rings[i][k].op != VT_CLOSEPATH) {
|
||||
area += rings[i][k].x * rings[i][(k + 1) % rings[i].size()].y;
|
||||
area -= rings[i][k].y * rings[i][(k + 1) % rings[i].size()].x;
|
||||
}
|
||||
}
|
||||
|
||||
areas[i] = area;
|
||||
if (areas[i] >= 0 || 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 (size_t 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 (size_t 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);
|
||||
} else {
|
||||
if (j != 0) {
|
||||
printf(", ");
|
||||
}
|
||||
|
||||
printf("[ %f, %f ]", rings[i][0].lon, rings[i][0].lat);
|
||||
}
|
||||
}
|
||||
|
||||
state = 2;
|
||||
}
|
||||
|
||||
if (outer > 1) {
|
||||
printf(" ] ] ]");
|
||||
} else {
|
||||
printf(" ] ]");
|
||||
}
|
||||
}
|
||||
|
||||
printf(" } }\n");
|
||||
if (to_decode.size() != 0 && !to_decode.count(layer.name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (describe) {
|
||||
printf("] }\n");
|
||||
if (!pipeline) {
|
||||
if (true) {
|
||||
if (!first_layer) {
|
||||
state.json_comma_newline();
|
||||
}
|
||||
|
||||
state.json_write_hash();
|
||||
|
||||
state.json_write_string("type");
|
||||
state.json_write_string("FeatureCollection");
|
||||
|
||||
state.json_write_string("properties");
|
||||
state.json_write_hash();
|
||||
|
||||
state.json_write_string("layer");
|
||||
state.json_write_string(layer.name);
|
||||
|
||||
state.json_write_string("version");
|
||||
state.json_write_signed(layer.version);
|
||||
|
||||
state.json_write_string("extent");
|
||||
state.json_write_signed(layer.extent);
|
||||
|
||||
state.json_end_hash();
|
||||
|
||||
state.json_write_string("features");
|
||||
state.json_write_array();
|
||||
|
||||
state.json_write_newline();
|
||||
first_layer = false;
|
||||
}
|
||||
}
|
||||
|
||||
// X and Y are unsigned, so no need to check <0
|
||||
if (x > (1ULL << z) || y > (1ULL << z)) {
|
||||
fprintf(stderr, "Impossible tile %d/%u/%u\n", z, x, y);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
layer_to_geojson(layer, z, x, y, !pipeline, pipeline, pipeline, false, 0, 0, 0, !force, state);
|
||||
|
||||
if (!pipeline) {
|
||||
if (true) {
|
||||
state.json_end_array();
|
||||
state.json_end_hash();
|
||||
state.json_write_newline();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printf("] }\n");
|
||||
if (!pipeline) {
|
||||
state.json_end_array();
|
||||
state.json_end_hash();
|
||||
state.json_write_newline();
|
||||
}
|
||||
}
|
||||
|
||||
void decode(char *fname, int z, unsigned x, unsigned y) {
|
||||
sqlite3 *db;
|
||||
void decode(char *fname, int z, unsigned x, unsigned y, std::set<std::string> const &to_decode, bool pipeline, bool stats, std::set<std::string> const &exclude_meta) {
|
||||
sqlite3 *db = NULL;
|
||||
bool isdir = false;
|
||||
int oz = z;
|
||||
unsigned ox = x, oy = y;
|
||||
json_writer state(stdout);
|
||||
|
||||
int fd = open(fname, O_RDONLY);
|
||||
int fd = open(fname, O_RDONLY | O_CLOEXEC);
|
||||
if (fd >= 0) {
|
||||
struct stat st;
|
||||
if (fstat(fd, &st) == 0) {
|
||||
@ -345,7 +240,7 @@ void decode(char *fname, int z, unsigned x, unsigned y) {
|
||||
if (strcmp(map, "SQLite format 3") != 0) {
|
||||
if (z >= 0) {
|
||||
std::string s = std::string(map, st.st_size);
|
||||
handle(s, z, x, y, 1);
|
||||
handle(s, z, x, y, to_decode, pipeline, stats, state);
|
||||
munmap(map, st.st_size);
|
||||
return;
|
||||
} else {
|
||||
@ -359,72 +254,188 @@ void decode(char *fname, int z, unsigned x, unsigned y) {
|
||||
} else {
|
||||
perror("fstat");
|
||||
}
|
||||
close(fd);
|
||||
if (close(fd) != 0) {
|
||||
perror("close");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
perror(fname);
|
||||
}
|
||||
|
||||
if (sqlite3_open(fname, &db) != SQLITE_OK) {
|
||||
fprintf(stderr, "%s: %s\n", fname, sqlite3_errmsg(db));
|
||||
exit(EXIT_FAILURE);
|
||||
struct stat st;
|
||||
std::vector<zxy> tiles;
|
||||
if (stat(fname, &st) == 0 && (st.st_mode & S_IFDIR) != 0) {
|
||||
isdir = true;
|
||||
|
||||
db = dirmeta2tmp(fname);
|
||||
tiles = enumerate_dirtiles(fname, minzoom, maxzoom);
|
||||
} else {
|
||||
if (sqlite3_open(fname, &db) != SQLITE_OK) {
|
||||
fprintf(stderr, "%s: %s\n", fname, sqlite3_errmsg(db));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
char *err = NULL;
|
||||
if (sqlite3_exec(db, "PRAGMA integrity_check;", NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "%s: integrity_check: %s\n", fname, err);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (z < 0) {
|
||||
printf("{ \"type\": \"FeatureCollection\", \"properties\": {\n");
|
||||
|
||||
const char *sql2 = "SELECT name, value from metadata order by name;";
|
||||
sqlite3_stmt *stmt2;
|
||||
if (sqlite3_prepare_v2(db, sql2, -1, &stmt2, NULL) != SQLITE_OK) {
|
||||
fprintf(stderr, "%s: select failed: %s\n", fname, sqlite3_errmsg(db));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int within = 0;
|
||||
while (sqlite3_step(stmt2) == SQLITE_ROW) {
|
||||
if (within) {
|
||||
printf(",\n");
|
||||
|
||||
if (!pipeline && !stats) {
|
||||
state.json_write_hash();
|
||||
|
||||
state.json_write_string("type");
|
||||
state.json_write_string("FeatureCollection");
|
||||
|
||||
state.json_write_string("properties");
|
||||
state.json_write_hash();
|
||||
state.json_write_newline();
|
||||
|
||||
const char *sql2 = "SELECT name, value from metadata order by name;";
|
||||
sqlite3_stmt *stmt2;
|
||||
if (sqlite3_prepare_v2(db, sql2, -1, &stmt2, NULL) != SQLITE_OK) {
|
||||
fprintf(stderr, "%s: select failed: %s\n", fname, sqlite3_errmsg(db));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
within = 1;
|
||||
|
||||
const unsigned char *name = sqlite3_column_text(stmt2, 0);
|
||||
const unsigned char *value = sqlite3_column_text(stmt2, 1);
|
||||
while (sqlite3_step(stmt2) == SQLITE_ROW) {
|
||||
const unsigned char *name = sqlite3_column_text(stmt2, 0);
|
||||
const unsigned char *value = sqlite3_column_text(stmt2, 1);
|
||||
|
||||
printq((char *) name);
|
||||
printf(": ");
|
||||
printq((char *) value);
|
||||
}
|
||||
if (name == NULL || value == NULL) {
|
||||
fprintf(stderr, "Corrupt mbtiles file: null metadata\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt2);
|
||||
if (exclude_meta.count((char *) name) == 0) {
|
||||
if (within) {
|
||||
state.json_comma_newline();
|
||||
}
|
||||
within = 1;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
printf("\n}, \"features\": [\n");
|
||||
|
||||
within = 0;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
if (within) {
|
||||
printf(",\n");
|
||||
state.json_write_string((char *) name);
|
||||
state.json_write_string((char *) value);
|
||||
}
|
||||
}
|
||||
within = 1;
|
||||
|
||||
int len = sqlite3_column_bytes(stmt, 0);
|
||||
int tz = sqlite3_column_int(stmt, 1);
|
||||
int tx = sqlite3_column_int(stmt, 2);
|
||||
int ty = sqlite3_column_int(stmt, 3);
|
||||
ty = (1LL << tz) - 1 - ty;
|
||||
const char *s = (const char *) sqlite3_column_blob(stmt, 0);
|
||||
state.json_write_newline();
|
||||
state.wantnl = false; // XXX
|
||||
|
||||
handle(std::string(s, len), tz, tx, ty, 1);
|
||||
sqlite3_finalize(stmt2);
|
||||
}
|
||||
|
||||
printf("] }\n");
|
||||
if (stats) {
|
||||
state.json_write_array();
|
||||
state.json_write_newline();
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
if (!pipeline && !stats) {
|
||||
state.json_end_hash();
|
||||
|
||||
state.json_write_string("features");
|
||||
state.json_write_array();
|
||||
state.json_write_newline();
|
||||
}
|
||||
|
||||
if (isdir) {
|
||||
within = 0;
|
||||
for (size_t i = 0; i < tiles.size(); i++) {
|
||||
if (!pipeline && !stats) {
|
||||
if (within) {
|
||||
state.json_comma_newline();
|
||||
}
|
||||
within = 1;
|
||||
}
|
||||
if (stats) {
|
||||
if (within) {
|
||||
state.json_comma_newline();
|
||||
}
|
||||
within = 1;
|
||||
}
|
||||
|
||||
std::string fn = std::string(fname) + "/" + tiles[i].path();
|
||||
FILE *f = fopen(fn.c_str(), "rb");
|
||||
if (f == NULL) {
|
||||
perror(fn.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::string s;
|
||||
char buf[2000];
|
||||
ssize_t n;
|
||||
while ((n = fread(buf, 1, 2000, f)) > 0) {
|
||||
s.append(std::string(buf, n));
|
||||
}
|
||||
fclose(f);
|
||||
|
||||
handle(s, tiles[i].z, tiles[i].x, tiles[i].y, to_decode, pipeline, stats, state);
|
||||
}
|
||||
} else {
|
||||
const char *sql = "SELECT tile_data, zoom_level, tile_column, tile_row from tiles where zoom_level between ? and ? 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, minzoom);
|
||||
sqlite3_bind_int(stmt, 2, maxzoom);
|
||||
|
||||
within = 0;
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
if (!pipeline && !stats) {
|
||||
if (within) {
|
||||
state.json_comma_newline();
|
||||
}
|
||||
within = 1;
|
||||
}
|
||||
if (stats) {
|
||||
if (within) {
|
||||
state.json_comma_newline();
|
||||
}
|
||||
within = 1;
|
||||
}
|
||||
|
||||
int len = sqlite3_column_bytes(stmt, 0);
|
||||
int tz = sqlite3_column_int(stmt, 1);
|
||||
int tx = sqlite3_column_int(stmt, 2);
|
||||
int ty = sqlite3_column_int(stmt, 3);
|
||||
|
||||
if (tz < 0 || tz >= 32) {
|
||||
fprintf(stderr, "Impossible zoom level %d in mbtiles\n", tz);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
ty = (1LL << tz) - 1 - ty;
|
||||
const char *s = (const char *) sqlite3_column_blob(stmt, 0);
|
||||
|
||||
if (s == NULL) {
|
||||
fprintf(stderr, "Corrupt mbtiles file: null entry in tiles table\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
handle(std::string(s, len), tz, tx, ty, to_decode, pipeline, stats, state);
|
||||
}
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
if (!pipeline && !stats) {
|
||||
state.json_end_array();
|
||||
state.json_end_hash();
|
||||
state.json_write_newline();
|
||||
}
|
||||
if (stats) {
|
||||
state.json_end_array();
|
||||
state.json_write_newline();
|
||||
}
|
||||
if (pipeline) {
|
||||
state.json_write_newline();
|
||||
}
|
||||
} else {
|
||||
int handled = 0;
|
||||
while (z >= 0 && !handled) {
|
||||
@ -443,11 +454,16 @@ void decode(char *fname, int z, unsigned x, unsigned y) {
|
||||
int len = sqlite3_column_bytes(stmt, 0);
|
||||
const char *s = (const char *) sqlite3_column_blob(stmt, 0);
|
||||
|
||||
if (s == NULL) {
|
||||
fprintf(stderr, "Corrupt mbtiles file: null entry in tiles table\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
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);
|
||||
handle(std::string(s, len), z, x, y, to_decode, pipeline, stats, state);
|
||||
handled = 1;
|
||||
}
|
||||
|
||||
@ -466,7 +482,7 @@ void decode(char *fname, int z, unsigned x, unsigned y) {
|
||||
}
|
||||
|
||||
void usage(char **argv) {
|
||||
fprintf(stderr, "Usage: %s [-t projection] file.mbtiles zoom x y\n", argv[0]);
|
||||
fprintf(stderr, "Usage: %s [-s projection] [-Z minzoom] [-z maxzoom] [-l layer ...] file.mbtiles [zoom x y]\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
@ -474,22 +490,80 @@ int main(int argc, char **argv) {
|
||||
extern int optind;
|
||||
extern char *optarg;
|
||||
int i;
|
||||
std::set<std::string> to_decode;
|
||||
bool pipeline = false;
|
||||
bool stats = false;
|
||||
std::set<std::string> exclude_meta;
|
||||
|
||||
while ((i = getopt(argc, argv, "t:")) != -1) {
|
||||
struct option long_options[] = {
|
||||
{"projection", required_argument, 0, 's'},
|
||||
{"maximum-zoom", required_argument, 0, 'z'},
|
||||
{"minimum-zoom", required_argument, 0, 'Z'},
|
||||
{"layer", required_argument, 0, 'l'},
|
||||
{"tag-layer-and-zoom", no_argument, 0, 'c'},
|
||||
{"stats", no_argument, 0, 'S'},
|
||||
{"force", no_argument, 0, 'f'},
|
||||
{"exclude-metadata-row", required_argument, 0, 'x'},
|
||||
{0, 0, 0, 0},
|
||||
};
|
||||
|
||||
std::string getopt_str;
|
||||
for (size_t lo = 0; long_options[lo].name != NULL; lo++) {
|
||||
if (long_options[lo].val > ' ') {
|
||||
getopt_str.push_back(long_options[lo].val);
|
||||
|
||||
if (long_options[lo].has_arg == required_argument) {
|
||||
getopt_str.push_back(':');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, NULL)) != -1) {
|
||||
switch (i) {
|
||||
case 't':
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case 's':
|
||||
set_projection_or_exit(optarg);
|
||||
break;
|
||||
|
||||
case 'z':
|
||||
maxzoom = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'Z':
|
||||
minzoom = atoi(optarg);
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
to_decode.insert(optarg);
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
pipeline = true;
|
||||
break;
|
||||
|
||||
case 'S':
|
||||
stats = true;
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
force = true;
|
||||
break;
|
||||
|
||||
case 'x':
|
||||
exclude_meta.insert(optarg);
|
||||
break;
|
||||
|
||||
default:
|
||||
usage(argv);
|
||||
}
|
||||
}
|
||||
|
||||
if (argc == optind + 4) {
|
||||
decode(argv[optind], atoi(argv[optind + 1]), atoi(argv[optind + 2]), atoi(argv[optind + 3]));
|
||||
decode(argv[optind], atoi(argv[optind + 1]), atoi(argv[optind + 2]), atoi(argv[optind + 3]), to_decode, pipeline, stats, exclude_meta);
|
||||
} else if (argc == optind + 1) {
|
||||
decode(argv[optind], -1, -1, -1);
|
||||
decode(argv[optind], -1, -1, -1, to_decode, pipeline, stats, exclude_meta);
|
||||
} else {
|
||||
usage(argv);
|
||||
}
|
||||
|
211
dirtiles.cpp
Normal file
211
dirtiles.cpp
Normal file
@ -0,0 +1,211 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <dirent.h>
|
||||
#include <limits.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sqlite3.h>
|
||||
#include "jsonpull/jsonpull.h"
|
||||
#include "dirtiles.hpp"
|
||||
|
||||
std::string dir_read_tile(std::string base, struct zxy tile) {
|
||||
std::ifstream pbfFile(base + "/" + tile.path(), std::ios::in | std::ios::binary);
|
||||
std::ostringstream contents;
|
||||
contents << pbfFile.rdbuf();
|
||||
pbfFile.close();
|
||||
|
||||
return (contents.str());
|
||||
}
|
||||
|
||||
void dir_write_tile(const char *outdir, int z, int tx, int ty, std::string const &pbf) {
|
||||
mkdir(outdir, S_IRWXU | S_IRWXG | S_IRWXO);
|
||||
std::string curdir(outdir);
|
||||
std::string slash("/");
|
||||
std::string newdir = curdir + slash + std::to_string(z);
|
||||
mkdir(newdir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
|
||||
newdir = newdir + "/" + std::to_string(tx);
|
||||
mkdir(newdir.c_str(), S_IRWXU | S_IRWXG | S_IRWXO);
|
||||
newdir = newdir + "/" + std::to_string(ty) + ".pbf";
|
||||
|
||||
struct stat st;
|
||||
if (stat(newdir.c_str(), &st) == 0) {
|
||||
fprintf(stderr, "Can't write tile to already existing %s\n", newdir.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::ofstream pbfFile(newdir, std::ios::out | std::ios::binary);
|
||||
pbfFile.write(pbf.data(), pbf.size());
|
||||
pbfFile.close();
|
||||
}
|
||||
|
||||
static bool numeric(const char *s) {
|
||||
if (*s == '\0') {
|
||||
return false;
|
||||
}
|
||||
for (; *s != 0; s++) {
|
||||
if (*s < '0' || *s > '9') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool pbfname(const char *s) {
|
||||
while (*s >= '0' && *s <= '9') {
|
||||
s++;
|
||||
}
|
||||
|
||||
return strcmp(s, ".pbf") == 0 || strcmp(s, ".mvt") == 0;
|
||||
}
|
||||
|
||||
void check_dir(const char *dir, char **argv, bool force, bool forcetable) {
|
||||
struct stat st;
|
||||
|
||||
mkdir(dir, S_IRWXU | S_IRWXG | S_IRWXO);
|
||||
std::string meta = std::string(dir) + "/" + "metadata.json";
|
||||
if (force) {
|
||||
unlink(meta.c_str()); // error OK since it may not exist;
|
||||
} else {
|
||||
if (stat(meta.c_str(), &st) == 0) {
|
||||
fprintf(stderr, "%s: Tileset \"%s\" already exists. You can use --force if you want to delete the old tileset.\n", argv[0], dir);
|
||||
fprintf(stderr, "%s: %s: file exists\n", argv[0], meta.c_str());
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (forcetable) {
|
||||
// Don't clear existing tiles
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<zxy> tiles = enumerate_dirtiles(dir, INT_MIN, INT_MAX);
|
||||
|
||||
for (size_t i = 0; i < tiles.size(); i++) {
|
||||
std::string fn = std::string(dir) + "/" + tiles[i].path();
|
||||
|
||||
if (force) {
|
||||
if (unlink(fn.c_str()) != 0) {
|
||||
perror(fn.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "%s: file exists\n", fn.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<zxy> enumerate_dirtiles(const char *fname, int minzoom, int maxzoom) {
|
||||
std::vector<zxy> tiles;
|
||||
|
||||
DIR *d1 = opendir(fname);
|
||||
if (d1 != NULL) {
|
||||
struct dirent *dp;
|
||||
while ((dp = readdir(d1)) != NULL) {
|
||||
if (numeric(dp->d_name) && atoi(dp->d_name) >= minzoom && atoi(dp->d_name) <= maxzoom) {
|
||||
std::string z = std::string(fname) + "/" + dp->d_name;
|
||||
int tz = atoi(dp->d_name);
|
||||
|
||||
DIR *d2 = opendir(z.c_str());
|
||||
if (d2 == NULL) {
|
||||
perror(z.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct dirent *dp2;
|
||||
while ((dp2 = readdir(d2)) != NULL) {
|
||||
if (numeric(dp2->d_name)) {
|
||||
std::string x = z + "/" + dp2->d_name;
|
||||
int tx = atoi(dp2->d_name);
|
||||
|
||||
DIR *d3 = opendir(x.c_str());
|
||||
if (d3 == NULL) {
|
||||
perror(x.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
struct dirent *dp3;
|
||||
while ((dp3 = readdir(d3)) != NULL) {
|
||||
if (pbfname(dp3->d_name)) {
|
||||
int ty = atoi(dp3->d_name);
|
||||
zxy tile(tz, tx, ty);
|
||||
if (strstr(dp3->d_name, ".mvt") != NULL) {
|
||||
tile.extension = ".mvt";
|
||||
}
|
||||
|
||||
tiles.push_back(tile);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(d3);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(d2);
|
||||
}
|
||||
}
|
||||
|
||||
closedir(d1);
|
||||
}
|
||||
|
||||
std::sort(tiles.begin(), tiles.end());
|
||||
return tiles;
|
||||
}
|
||||
|
||||
sqlite3 *dirmeta2tmp(const char *fname) {
|
||||
sqlite3 *db;
|
||||
char *err = NULL;
|
||||
|
||||
if (sqlite3_open("", &db) != SQLITE_OK) {
|
||||
fprintf(stderr, "Temporary db: %s\n", sqlite3_errmsg(db));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (sqlite3_exec(db, "CREATE TABLE metadata (name text, value text);", NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "Create metadata table: %s\n", err);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::string name = fname;
|
||||
name += "/metadata.json";
|
||||
|
||||
FILE *f = fopen(name.c_str(), "r");
|
||||
if (f == NULL) {
|
||||
perror(name.c_str());
|
||||
} else {
|
||||
json_pull *jp = json_begin_file(f);
|
||||
json_object *o = json_read_tree(jp);
|
||||
if (o == NULL) {
|
||||
fprintf(stderr, "%s: metadata parsing error: %s\n", name.c_str(), jp->error);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (o->type != JSON_HASH) {
|
||||
fprintf(stderr, "%s: bad metadata format\n", name.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < o->length; i++) {
|
||||
if (o->keys[i]->type != JSON_STRING || o->values[i]->type != JSON_STRING) {
|
||||
fprintf(stderr, "%s: non-string in metadata\n", name.c_str());
|
||||
}
|
||||
|
||||
char *sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES (%Q, %Q);", o->keys[i]->string, o->values[i]->string);
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set %s in metadata: %s\n", o->keys[i]->string, err);
|
||||
}
|
||||
sqlite3_free(sql);
|
||||
}
|
||||
|
||||
json_end(jp);
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
49
dirtiles.hpp
Normal file
49
dirtiles.hpp
Normal file
@ -0,0 +1,49 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifndef DIRTILES_HPP
|
||||
#define DIRTILES_HPP
|
||||
|
||||
void dir_write_tile(const char *outdir, int z, int tx, int ty, std::string const &pbf);
|
||||
|
||||
void check_dir(const char *d, char **argv, bool force, bool forcetable);
|
||||
|
||||
struct zxy {
|
||||
long long z;
|
||||
long long x;
|
||||
long long y;
|
||||
std::string extension = ".pbf";
|
||||
|
||||
zxy(int _z, int _x, int _y)
|
||||
: z(_z), x(_x), y(_y) {
|
||||
}
|
||||
|
||||
bool operator<(const zxy &other) const {
|
||||
if (z < other.z) {
|
||||
return true;
|
||||
}
|
||||
if (z == other.z) {
|
||||
if (x < other.x) {
|
||||
return true;
|
||||
}
|
||||
if (x == other.x) {
|
||||
if (y > other.y) {
|
||||
return true; // reversed for TMS
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string path() {
|
||||
return std::to_string(z) + "/" + std::to_string(x) + "/" + std::to_string(y) + extension;
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<zxy> enumerate_dirtiles(const char *fname, int minzoom, int maxzoom);
|
||||
sqlite3 *dirmeta2tmp(const char *fname);
|
||||
std::string dir_read_tile(std::string pbfPath, struct zxy tile);
|
||||
|
||||
#endif
|
@ -11,6 +11,12 @@ void enumerate(char *fname) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
char *err = NULL;
|
||||
if (sqlite3_exec(db, "PRAGMA integrity_check;", NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "%s: integrity_check: %s\n", fname, err);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
const char *sql = "SELECT zoom_level, tile_column, tile_row from tiles order by zoom_level, tile_column, tile_row;";
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
@ -24,6 +30,11 @@ void enumerate(char *fname) {
|
||||
long long x = sqlite3_column_int(stmt, 1);
|
||||
long long y = sqlite3_column_int(stmt, 2);
|
||||
|
||||
if (zoom < 0 || zoom > 31) {
|
||||
fprintf(stderr, "Corrupt mbtiles file: impossible zoom level %lld\n", zoom);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
y = (1LL << zoom) - 1 - y;
|
||||
printf("%s %lld %lld %lld\n", fname, zoom, x, y);
|
||||
}
|
||||
|
347
evaluator.cpp
Normal file
347
evaluator.cpp
Normal file
@ -0,0 +1,347 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <map>
|
||||
#include "mvt.hpp"
|
||||
#include "evaluator.hpp"
|
||||
|
||||
int compare(mvt_value one, json_object *two, bool &fail) {
|
||||
if (one.type == mvt_string) {
|
||||
if (two->type != JSON_STRING) {
|
||||
fail = true;
|
||||
return false; // string vs non-string
|
||||
}
|
||||
|
||||
return strcmp(one.string_value.c_str(), two->string);
|
||||
}
|
||||
|
||||
if (one.type == mvt_double || one.type == mvt_float || one.type == mvt_int || one.type == mvt_uint || one.type == mvt_sint) {
|
||||
if (two->type != JSON_NUMBER) {
|
||||
fail = true;
|
||||
return false; // number vs non-number
|
||||
}
|
||||
|
||||
double v;
|
||||
if (one.type == mvt_double) {
|
||||
v = one.numeric_value.double_value;
|
||||
} else if (one.type == mvt_float) {
|
||||
v = one.numeric_value.float_value;
|
||||
} else if (one.type == mvt_int) {
|
||||
v = one.numeric_value.int_value;
|
||||
} else if (one.type == mvt_uint) {
|
||||
v = one.numeric_value.uint_value;
|
||||
} else if (one.type == mvt_sint) {
|
||||
v = one.numeric_value.sint_value;
|
||||
} else {
|
||||
fprintf(stderr, "Internal error: bad mvt type %d\n", one.type);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (v < two->number) {
|
||||
return -1;
|
||||
} else if (v > two->number) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (one.type == mvt_bool) {
|
||||
if (two->type != JSON_TRUE && two->type != JSON_FALSE) {
|
||||
fail = true;
|
||||
return false; // bool vs non-bool
|
||||
}
|
||||
|
||||
bool b = two->type != JSON_FALSE;
|
||||
return one.numeric_value.bool_value > b;
|
||||
}
|
||||
|
||||
if (one.type == mvt_null) {
|
||||
if (two->type != JSON_NULL) {
|
||||
fail = true;
|
||||
return false; // null vs non-null
|
||||
}
|
||||
|
||||
return 0; // null equals null
|
||||
}
|
||||
|
||||
fprintf(stderr, "Internal error: bad mvt type %d\n", one.type);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
bool eval(std::map<std::string, mvt_value> const &feature, json_object *f, std::set<std::string> &exclude_attributes) {
|
||||
if (f == NULL || f->type != JSON_ARRAY) {
|
||||
fprintf(stderr, "Filter is not an array: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (f->length < 1) {
|
||||
fprintf(stderr, "Array too small in filter: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (f->array[0]->type != JSON_STRING) {
|
||||
fprintf(stderr, "Filter operation is not a string: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (strcmp(f->array[0]->string, "has") == 0 ||
|
||||
strcmp(f->array[0]->string, "!has") == 0) {
|
||||
if (f->length != 2) {
|
||||
fprintf(stderr, "Wrong number of array elements in filter: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (strcmp(f->array[0]->string, "has") == 0) {
|
||||
if (f->array[1]->type != JSON_STRING) {
|
||||
fprintf(stderr, "\"has\" key is not a string: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return feature.count(std::string(f->array[1]->string)) != 0;
|
||||
}
|
||||
|
||||
if (strcmp(f->array[0]->string, "!has") == 0) {
|
||||
if (f->array[1]->type != JSON_STRING) {
|
||||
fprintf(stderr, "\"!has\" key is not a string: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return feature.count(std::string(f->array[1]->string)) == 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(f->array[0]->string, "==") == 0 ||
|
||||
strcmp(f->array[0]->string, "!=") == 0 ||
|
||||
strcmp(f->array[0]->string, ">") == 0 ||
|
||||
strcmp(f->array[0]->string, ">=") == 0 ||
|
||||
strcmp(f->array[0]->string, "<") == 0 ||
|
||||
strcmp(f->array[0]->string, "<=") == 0) {
|
||||
if (f->length != 3) {
|
||||
fprintf(stderr, "Wrong number of array elements in filter: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (f->array[1]->type != JSON_STRING) {
|
||||
fprintf(stderr, "\"!has\" key is not a string: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
auto ff = feature.find(std::string(f->array[1]->string));
|
||||
if (ff == feature.end()) {
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
const char *s = json_stringify(f);
|
||||
fprintf(stderr, "Warning: attribute not found for comparison: %s\n", s);
|
||||
free((void *) s);
|
||||
warned = true;
|
||||
}
|
||||
if (strcmp(f->array[0]->string, "!=") == 0) {
|
||||
return true; // attributes that aren't found are not equal
|
||||
}
|
||||
return false; // not found: comparison is false
|
||||
}
|
||||
|
||||
bool fail = false;
|
||||
int cmp = compare(ff->second, f->array[2], fail);
|
||||
|
||||
if (fail) {
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
const char *s = json_stringify(f);
|
||||
fprintf(stderr, "Warning: mismatched type in comparison: %s\n", s);
|
||||
free((void *) s);
|
||||
warned = true;
|
||||
}
|
||||
if (strcmp(f->array[0]->string, "!=") == 0) {
|
||||
return true; // mismatched types are not equal
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strcmp(f->array[0]->string, "==") == 0) {
|
||||
return cmp == 0;
|
||||
}
|
||||
if (strcmp(f->array[0]->string, "!=") == 0) {
|
||||
return cmp != 0;
|
||||
}
|
||||
if (strcmp(f->array[0]->string, ">") == 0) {
|
||||
return cmp > 0;
|
||||
}
|
||||
if (strcmp(f->array[0]->string, ">=") == 0) {
|
||||
return cmp >= 0;
|
||||
}
|
||||
if (strcmp(f->array[0]->string, "<") == 0) {
|
||||
return cmp < 0;
|
||||
}
|
||||
if (strcmp(f->array[0]->string, "<=") == 0) {
|
||||
return cmp <= 0;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Internal error: can't happen: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (strcmp(f->array[0]->string, "all") == 0 ||
|
||||
strcmp(f->array[0]->string, "any") == 0 ||
|
||||
strcmp(f->array[0]->string, "none") == 0) {
|
||||
bool v;
|
||||
|
||||
if (strcmp(f->array[0]->string, "all") == 0) {
|
||||
v = true;
|
||||
} else {
|
||||
v = false;
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < f->length; i++) {
|
||||
bool out = eval(feature, f->array[i], exclude_attributes);
|
||||
|
||||
if (strcmp(f->array[0]->string, "all") == 0) {
|
||||
v = v && out;
|
||||
if (!v) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
v = v || out;
|
||||
if (v) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(f->array[0]->string, "none") == 0) {
|
||||
return !v;
|
||||
} else {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(f->array[0]->string, "in") == 0 ||
|
||||
strcmp(f->array[0]->string, "!in") == 0) {
|
||||
if (f->length < 2) {
|
||||
fprintf(stderr, "Array too small in filter: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (f->array[1]->type != JSON_STRING) {
|
||||
fprintf(stderr, "\"!has\" key is not a string: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
auto ff = feature.find(std::string(f->array[1]->string));
|
||||
if (ff == feature.end()) {
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
const char *s = json_stringify(f);
|
||||
fprintf(stderr, "Warning: attribute not found for comparison: %s\n", s);
|
||||
free((void *) s);
|
||||
warned = true;
|
||||
}
|
||||
if (strcmp(f->array[0]->string, "!in") == 0) {
|
||||
return true; // attributes that aren't found are not in
|
||||
}
|
||||
return false; // not found: comparison is false
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
for (size_t i = 2; i < f->length; i++) {
|
||||
bool fail = false;
|
||||
int cmp = compare(ff->second, f->array[i], fail);
|
||||
|
||||
if (fail) {
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
const char *s = json_stringify(f);
|
||||
fprintf(stderr, "Warning: mismatched type in comparison: %s\n", s);
|
||||
free((void *) s);
|
||||
warned = true;
|
||||
}
|
||||
cmp = 1;
|
||||
}
|
||||
|
||||
if (cmp == 0) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(f->array[0]->string, "in") == 0) {
|
||||
return found;
|
||||
} else {
|
||||
return !found;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(f->array[0]->string, "attribute-filter") == 0) {
|
||||
if (f->length != 3) {
|
||||
fprintf(stderr, "Wrong number of array elements in filter: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (f->array[1]->type != JSON_STRING) {
|
||||
fprintf(stderr, "\"attribute-filter\" key is not a string: %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
bool ok = eval(feature, f->array[2], exclude_attributes);
|
||||
if (!ok) {
|
||||
exclude_attributes.insert(f->array[1]->string);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
fprintf(stderr, "Unknown filter %s\n", json_stringify(f));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
bool evaluate(std::map<std::string, mvt_value> const &feature, std::string const &layer, json_object *filter, std::set<std::string> &exclude_attributes) {
|
||||
if (filter == NULL || filter->type != JSON_HASH) {
|
||||
fprintf(stderr, "Error: filter is not a hash: %s\n", json_stringify(filter));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
json_object *f;
|
||||
|
||||
f = json_hash_get(filter, layer.c_str());
|
||||
if (ok && f != NULL) {
|
||||
ok = eval(feature, f, exclude_attributes);
|
||||
}
|
||||
|
||||
f = json_hash_get(filter, "*");
|
||||
if (ok && f != NULL) {
|
||||
ok = eval(feature, f, exclude_attributes);
|
||||
}
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
json_object *read_filter(const char *fname) {
|
||||
FILE *fp = fopen(fname, "r");
|
||||
if (fp == NULL) {
|
||||
perror(fname);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
json_pull *jp = json_begin_file(fp);
|
||||
json_object *filter = json_read_tree(jp);
|
||||
if (filter == NULL) {
|
||||
fprintf(stderr, "%s: %s\n", fname, jp->error);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
json_disconnect(filter);
|
||||
json_end(jp);
|
||||
fclose(fp);
|
||||
return filter;
|
||||
}
|
||||
|
||||
json_object *parse_filter(const char *s) {
|
||||
json_pull *jp = json_begin_string(s);
|
||||
json_object *filter = json_read_tree(jp);
|
||||
if (filter == NULL) {
|
||||
fprintf(stderr, "Could not parse filter %s\n", s);
|
||||
fprintf(stderr, "%s\n", jp->error);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
json_disconnect(filter);
|
||||
json_end(jp);
|
||||
return filter;
|
||||
}
|
14
evaluator.hpp
Normal file
14
evaluator.hpp
Normal file
@ -0,0 +1,14 @@
|
||||
#ifndef EVALUATOR_HPP
|
||||
#define EVALUATOR HPP
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include "jsonpull/jsonpull.h"
|
||||
#include "mvt.hpp"
|
||||
|
||||
bool evaluate(std::map<std::string, mvt_value> const &feature, std::string const &layer, json_object *filter, std::set<std::string> &exclude_attributes);
|
||||
json_object *parse_filter(const char *s);
|
||||
json_object *read_filter(const char *fname);
|
||||
|
||||
#endif
|
27
filters/limit-tiles-to-bbox
Executable file
27
filters/limit-tiles-to-bbox
Executable file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/perl
|
||||
|
||||
use Math::Trig;
|
||||
use strict;
|
||||
|
||||
# http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
|
||||
sub getTileNumber {
|
||||
my ($lat, $lon, $zoom) = @_;
|
||||
my $xtile = int(($lon + 180) / 360 * 2 ** $zoom);
|
||||
my $ytile = int((1 - log(tan(deg2rad($lat)) + sec(deg2rad($lat))) / pi) / 2 * 2 ** $zoom);
|
||||
return ($xtile, $ytile);
|
||||
}
|
||||
|
||||
my ($minlon, $minlat, $maxlon, $maxlat, $z, $x, $y) = @ARGV;
|
||||
|
||||
my ($x1, $y1) = getTileNumber($maxlat, $minlon, $z);
|
||||
my ($x2, $y2) = getTileNumber($minlat, $maxlon, $z);
|
||||
|
||||
if ($x >= $x1 && $x <= $x2 && $y >= $y1 && $y <= $y2) {
|
||||
while (<STDIN>) {
|
||||
print;
|
||||
}
|
||||
} else {
|
||||
while (<STDIN>) {
|
||||
|
||||
}
|
||||
}
|
585
geobuf.cpp
Normal file
585
geobuf.cpp
Normal file
@ -0,0 +1,585 @@
|
||||
#include <stdio.h>
|
||||
#include <string>
|
||||
#include <limits.h>
|
||||
#include <pthread.h>
|
||||
#include "mvt.hpp"
|
||||
#include "serial.hpp"
|
||||
#include "geobuf.hpp"
|
||||
#include "geojson.hpp"
|
||||
#include "projection.hpp"
|
||||
#include "main.hpp"
|
||||
#include "protozero/varint.hpp"
|
||||
#include "protozero/pbf_reader.hpp"
|
||||
#include "protozero/pbf_writer.hpp"
|
||||
#include "milo/dtoa_milo.h"
|
||||
#include "jsonpull/jsonpull.h"
|
||||
#include "text.hpp"
|
||||
|
||||
#define POINT 0
|
||||
#define MULTIPOINT 1
|
||||
#define LINESTRING 2
|
||||
#define MULTILINESTRING 3
|
||||
#define POLYGON 4
|
||||
#define MULTIPOLYGON 5
|
||||
|
||||
struct queued_feature {
|
||||
protozero::pbf_reader pbf{};
|
||||
size_t dim = 0;
|
||||
double e = 0;
|
||||
std::vector<std::string> *keys = NULL;
|
||||
std::vector<struct serialization_state> *sst = NULL;
|
||||
int layer = 0;
|
||||
std::string layername = "";
|
||||
};
|
||||
|
||||
static std::vector<queued_feature> feature_queue;
|
||||
|
||||
void ensureDim(size_t dim) {
|
||||
if (dim < 2) {
|
||||
fprintf(stderr, "Geometry has fewer than 2 dimensions: %zu\n", dim);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
serial_val readValue(protozero::pbf_reader &pbf) {
|
||||
serial_val sv;
|
||||
sv.type = mvt_null;
|
||||
sv.s = "null";
|
||||
|
||||
while (pbf.next()) {
|
||||
switch (pbf.tag()) {
|
||||
case 1:
|
||||
sv.type = mvt_string;
|
||||
sv.s = pbf.get_string();
|
||||
break;
|
||||
|
||||
case 2:
|
||||
sv.type = mvt_double;
|
||||
sv.s = milo::dtoa_milo(pbf.get_double());
|
||||
break;
|
||||
|
||||
case 3:
|
||||
sv.type = mvt_double;
|
||||
sv.s = std::to_string(pbf.get_uint64());
|
||||
break;
|
||||
|
||||
case 4:
|
||||
sv.type = mvt_double;
|
||||
sv.s = std::to_string(-(long long) pbf.get_uint64());
|
||||
break;
|
||||
|
||||
case 5:
|
||||
sv.type = mvt_bool;
|
||||
if (pbf.get_bool()) {
|
||||
sv.s = "true";
|
||||
} else {
|
||||
sv.s = "false";
|
||||
}
|
||||
break;
|
||||
|
||||
case 6:
|
||||
sv.type = mvt_string; // stringified JSON
|
||||
sv.s = pbf.get_string();
|
||||
|
||||
if (sv.s == "null") {
|
||||
sv.type = mvt_null;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
pbf.skip();
|
||||
}
|
||||
}
|
||||
|
||||
return sv;
|
||||
}
|
||||
|
||||
drawvec readPoint(std::vector<long long> &coords, size_t dim, double e) {
|
||||
ensureDim(dim);
|
||||
|
||||
long long x, y;
|
||||
projection->project(coords[0] / e, coords[1] / e, 32, &x, &y);
|
||||
drawvec dv;
|
||||
dv.push_back(draw(VT_MOVETO, x, y));
|
||||
return dv;
|
||||
}
|
||||
|
||||
drawvec readLinePart(std::vector<long long> &coords, size_t dim, double e, size_t start, size_t end, bool closed) {
|
||||
ensureDim(dim);
|
||||
|
||||
drawvec dv;
|
||||
std::vector<long long> prev;
|
||||
std::vector<double> p;
|
||||
prev.resize(dim);
|
||||
p.resize(dim);
|
||||
|
||||
for (size_t i = start; i + dim - 1 < end; i += dim) {
|
||||
if (i + dim - 1 >= coords.size()) {
|
||||
fprintf(stderr, "Internal error: line segment %zu vs %zu\n", i + dim - 1, coords.size());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
for (size_t d = 0; d < dim; d++) {
|
||||
prev[d] += coords[i + d];
|
||||
p[d] = prev[d] / e;
|
||||
}
|
||||
|
||||
long long x, y;
|
||||
projection->project(p[0], p[1], 32, &x, &y);
|
||||
|
||||
if (i == start) {
|
||||
dv.push_back(draw(VT_MOVETO, x, y));
|
||||
} else {
|
||||
dv.push_back(draw(VT_LINETO, x, y));
|
||||
}
|
||||
}
|
||||
|
||||
if (closed && dv.size() > 0) {
|
||||
dv.push_back(draw(VT_LINETO, dv[0].x, dv[0].y));
|
||||
}
|
||||
|
||||
return dv;
|
||||
}
|
||||
|
||||
drawvec readLine(std::vector<long long> &coords, size_t dim, double e, bool closed) {
|
||||
return readLinePart(coords, dim, e, 0, coords.size(), closed);
|
||||
}
|
||||
|
||||
drawvec readMultiLine(std::vector<long long> &coords, std::vector<int> &lengths, size_t dim, double e, bool closed) {
|
||||
if (lengths.size() == 0) {
|
||||
return readLinePart(coords, dim, e, 0, coords.size(), closed);
|
||||
}
|
||||
|
||||
drawvec dv;
|
||||
size_t here = 0;
|
||||
for (size_t i = 0; i < lengths.size(); i++) {
|
||||
drawvec dv2 = readLinePart(coords, dim, e, here, here + lengths[i] * dim, closed);
|
||||
here += lengths[i] * dim;
|
||||
|
||||
for (size_t j = 0; j < dv2.size(); j++) {
|
||||
dv.push_back(dv2[j]);
|
||||
}
|
||||
}
|
||||
|
||||
return dv;
|
||||
}
|
||||
|
||||
drawvec readMultiPolygon(std::vector<long long> &coords, std::vector<int> &lengths, size_t dim, double e) {
|
||||
ensureDim(dim);
|
||||
|
||||
if (lengths.size() == 0) {
|
||||
return readLinePart(coords, dim, e, 0, coords.size(), true);
|
||||
}
|
||||
|
||||
size_t polys = lengths[0];
|
||||
size_t n = 1;
|
||||
size_t here = 0;
|
||||
drawvec dv;
|
||||
|
||||
for (size_t i = 0; i < polys; i++) {
|
||||
size_t rings = lengths[n++];
|
||||
|
||||
for (size_t j = 0; j < rings; j++) {
|
||||
drawvec dv2 = readLinePart(coords, dim, e, here, here + lengths[n] * dim, true);
|
||||
here += lengths[n] * dim;
|
||||
n++;
|
||||
|
||||
for (size_t k = 0; k < dv2.size(); k++) {
|
||||
dv.push_back(dv2[k]);
|
||||
}
|
||||
}
|
||||
|
||||
dv.push_back(draw(VT_CLOSEPATH, 0, 0)); // mark that the next ring is outer
|
||||
}
|
||||
|
||||
return dv;
|
||||
}
|
||||
|
||||
struct drawvec_type {
|
||||
drawvec dv{};
|
||||
int type = 0;
|
||||
};
|
||||
|
||||
std::vector<drawvec_type> readGeometry(protozero::pbf_reader &pbf, size_t dim, double e, std::vector<std::string> &keys) {
|
||||
std::vector<drawvec_type> ret;
|
||||
std::vector<long long> coords;
|
||||
std::vector<int> lengths;
|
||||
int type = -1;
|
||||
|
||||
while (pbf.next()) {
|
||||
switch (pbf.tag()) {
|
||||
case 1:
|
||||
type = pbf.get_enum();
|
||||
break;
|
||||
|
||||
case 2: {
|
||||
auto pi = pbf.get_packed_uint32();
|
||||
for (auto it = pi.first; it != pi.second; ++it) {
|
||||
lengths.push_back(*it);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 3: {
|
||||
auto pi = pbf.get_packed_sint64();
|
||||
for (auto it = pi.first; it != pi.second; ++it) {
|
||||
coords.push_back(*it);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 4: {
|
||||
protozero::pbf_reader geometry_reader(pbf.get_message());
|
||||
std::vector<drawvec_type> dv2 = readGeometry(geometry_reader, dim, e, keys);
|
||||
|
||||
for (size_t i = 0; i < dv2.size(); i++) {
|
||||
ret.push_back(dv2[i]);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
pbf.skip();
|
||||
}
|
||||
}
|
||||
|
||||
drawvec_type dv;
|
||||
if (type == POINT) {
|
||||
dv.dv = readPoint(coords, dim, e);
|
||||
} else if (type == MULTIPOINT) {
|
||||
dv.dv = readLine(coords, dim, e, false);
|
||||
} else if (type == LINESTRING) {
|
||||
dv.dv = readLine(coords, dim, e, false);
|
||||
} else if (type == POLYGON) {
|
||||
dv.dv = readMultiLine(coords, lengths, dim, e, true);
|
||||
} else if (type == MULTILINESTRING) {
|
||||
dv.dv = readMultiLine(coords, lengths, dim, e, false);
|
||||
} else if (type == MULTIPOLYGON) {
|
||||
dv.dv = readMultiPolygon(coords, lengths, dim, e);
|
||||
} else {
|
||||
// GeometryCollection
|
||||
return ret;
|
||||
}
|
||||
|
||||
dv.type = type / 2 + 1;
|
||||
ret.push_back(dv);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void readFeature(protozero::pbf_reader &pbf, size_t dim, double e, std::vector<std::string> &keys, struct serialization_state *sst, int layer, std::string layername) {
|
||||
std::vector<drawvec_type> dv;
|
||||
long long id = 0;
|
||||
bool has_id = false;
|
||||
std::vector<serial_val> values;
|
||||
std::map<std::string, serial_val> other;
|
||||
|
||||
std::vector<std::string> full_keys;
|
||||
std::vector<serial_val> full_values;
|
||||
|
||||
while (pbf.next()) {
|
||||
switch (pbf.tag()) {
|
||||
case 1: {
|
||||
protozero::pbf_reader geometry_reader(pbf.get_message());
|
||||
std::vector<drawvec_type> dv2 = readGeometry(geometry_reader, dim, e, keys);
|
||||
for (size_t i = 0; i < dv2.size(); i++) {
|
||||
dv.push_back(dv2[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 11: {
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
fprintf(stderr, "Non-numeric feature IDs not supported\n");
|
||||
warned = true;
|
||||
}
|
||||
pbf.skip();
|
||||
break;
|
||||
}
|
||||
|
||||
case 12:
|
||||
has_id = true;
|
||||
id = pbf.get_sint64();
|
||||
if (id < 0) {
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
fprintf(stderr, "Out of range feature id %lld\n", id);
|
||||
warned = true;
|
||||
}
|
||||
has_id = false;
|
||||
}
|
||||
break;
|
||||
|
||||
case 13: {
|
||||
protozero::pbf_reader value_reader(pbf.get_message());
|
||||
values.push_back(readValue(value_reader));
|
||||
break;
|
||||
}
|
||||
|
||||
case 14: {
|
||||
std::vector<size_t> properties;
|
||||
auto pi = pbf.get_packed_uint32();
|
||||
for (auto it = pi.first; it != pi.second; ++it) {
|
||||
properties.push_back(*it);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i + 1 < properties.size(); i += 2) {
|
||||
if (properties[i] >= keys.size()) {
|
||||
fprintf(stderr, "Out of bounds key: %zu in %zu\n", properties[i], keys.size());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (properties[i + 1] >= values.size()) {
|
||||
fprintf(stderr, "Out of bounds value: %zu in %zu\n", properties[i + 1], values.size());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
full_keys.push_back(keys[properties[i]]);
|
||||
full_values.push_back(values[properties[i + 1]]);
|
||||
}
|
||||
|
||||
values.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
case 15: {
|
||||
std::vector<size_t> misc;
|
||||
auto pi = pbf.get_packed_uint32();
|
||||
for (auto it = pi.first; it != pi.second; ++it) {
|
||||
misc.push_back(*it);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i + 1 < misc.size(); i += 2) {
|
||||
if (misc[i] >= keys.size()) {
|
||||
fprintf(stderr, "Out of bounds key: %zu in %zu\n", misc[i], keys.size());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (misc[i + 1] >= values.size()) {
|
||||
fprintf(stderr, "Out of bounds value: %zu in %zu\n", misc[i + 1], values.size());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
other.insert(std::pair<std::string, serial_val>(keys[misc[i]], values[misc[i + 1]]));
|
||||
}
|
||||
|
||||
values.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
pbf.skip();
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < dv.size(); i++) {
|
||||
serial_feature sf;
|
||||
|
||||
sf.layer = layer;
|
||||
sf.layername = layername;
|
||||
sf.segment = sst->segment;
|
||||
sf.has_id = has_id;
|
||||
sf.id = id;
|
||||
sf.has_tippecanoe_minzoom = false;
|
||||
sf.has_tippecanoe_maxzoom = false;
|
||||
sf.feature_minzoom = false;
|
||||
sf.seq = *(sst->layer_seq);
|
||||
sf.geometry = dv[i].dv;
|
||||
sf.t = dv[i].type;
|
||||
sf.full_keys = full_keys;
|
||||
sf.full_values = full_values;
|
||||
|
||||
auto tip = other.find("tippecanoe");
|
||||
if (tip != other.end()) {
|
||||
json_pull *jp = json_begin_string(tip->second.s.c_str());
|
||||
json_object *o = json_read_tree(jp);
|
||||
|
||||
if (o != NULL) {
|
||||
json_object *min = json_hash_get(o, "minzoom");
|
||||
if (min != NULL && (min->type == JSON_STRING || min->type == JSON_NUMBER)) {
|
||||
sf.has_tippecanoe_minzoom = true;
|
||||
sf.tippecanoe_minzoom = integer_zoom(sst->fname, min->string);
|
||||
}
|
||||
|
||||
json_object *max = json_hash_get(o, "maxzoom");
|
||||
if (max != NULL && (max->type == JSON_STRING || max->type == JSON_NUMBER)) {
|
||||
sf.has_tippecanoe_maxzoom = true;
|
||||
sf.tippecanoe_maxzoom = integer_zoom(sst->fname, max->string);
|
||||
}
|
||||
|
||||
json_object *tlayer = json_hash_get(o, "layer");
|
||||
if (tlayer != NULL && (tlayer->type == JSON_STRING || tlayer->type == JSON_NUMBER)) {
|
||||
sf.layername = tlayer->string;
|
||||
}
|
||||
}
|
||||
|
||||
json_free(o);
|
||||
json_end(jp);
|
||||
}
|
||||
|
||||
serialize_feature(sst, sf);
|
||||
}
|
||||
}
|
||||
|
||||
struct queue_run_arg {
|
||||
size_t start;
|
||||
size_t end;
|
||||
size_t segment;
|
||||
|
||||
queue_run_arg(size_t start1, size_t end1, size_t segment1)
|
||||
: start(start1), end(end1), segment(segment1) {
|
||||
}
|
||||
};
|
||||
|
||||
void *run_parse_feature(void *v) {
|
||||
struct queue_run_arg *qra = (struct queue_run_arg *) v;
|
||||
|
||||
for (size_t i = qra->start; i < qra->end; i++) {
|
||||
struct queued_feature &qf = feature_queue[i];
|
||||
readFeature(qf.pbf, qf.dim, qf.e, *qf.keys, &(*qf.sst)[qra->segment], qf.layer, qf.layername);
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void runQueue() {
|
||||
if (feature_queue.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<struct queue_run_arg> qra;
|
||||
|
||||
std::vector<pthread_t> pthreads;
|
||||
pthreads.resize(CPUS);
|
||||
|
||||
for (size_t i = 0; i < CPUS; i++) {
|
||||
*((*(feature_queue[0].sst))[i].layer_seq) = *((*(feature_queue[0].sst))[0].layer_seq) + feature_queue.size() * i / CPUS;
|
||||
|
||||
qra.push_back(queue_run_arg(
|
||||
feature_queue.size() * i / CPUS,
|
||||
feature_queue.size() * (i + 1) / CPUS,
|
||||
i));
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < CPUS; i++) {
|
||||
if (pthread_create(&pthreads[i], NULL, run_parse_feature, &qra[i]) != 0) {
|
||||
perror("pthread_create");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < CPUS; i++) {
|
||||
void *retval;
|
||||
|
||||
if (pthread_join(pthreads[i], &retval) != 0) {
|
||||
perror("pthread_join");
|
||||
}
|
||||
}
|
||||
|
||||
// Lack of atomicity is OK, since we are single-threaded again here
|
||||
long long was = *((*(feature_queue[0].sst))[CPUS - 1].layer_seq);
|
||||
*((*(feature_queue[0].sst))[0].layer_seq) = was;
|
||||
feature_queue.clear();
|
||||
}
|
||||
|
||||
void queueFeature(protozero::pbf_reader &pbf, size_t dim, double e, std::vector<std::string> &keys, std::vector<struct serialization_state> *sst, int layer, std::string layername) {
|
||||
struct queued_feature qf;
|
||||
qf.pbf = pbf;
|
||||
qf.dim = dim;
|
||||
qf.e = e;
|
||||
qf.keys = &keys;
|
||||
qf.sst = sst;
|
||||
qf.layer = layer;
|
||||
qf.layername = layername;
|
||||
|
||||
feature_queue.push_back(qf);
|
||||
|
||||
if (feature_queue.size() > CPUS * 500) {
|
||||
runQueue();
|
||||
}
|
||||
}
|
||||
|
||||
void outBareGeometry(drawvec const &dv, int type, struct serialization_state *sst, int layer, std::string layername) {
|
||||
serial_feature sf;
|
||||
|
||||
sf.layer = layer;
|
||||
sf.layername = layername;
|
||||
sf.segment = sst->segment;
|
||||
sf.has_id = false;
|
||||
sf.has_tippecanoe_minzoom = false;
|
||||
sf.has_tippecanoe_maxzoom = false;
|
||||
sf.feature_minzoom = false;
|
||||
sf.seq = (*sst->layer_seq);
|
||||
sf.geometry = dv;
|
||||
sf.t = type;
|
||||
|
||||
serialize_feature(sst, sf);
|
||||
}
|
||||
|
||||
void readFeatureCollection(protozero::pbf_reader &pbf, size_t dim, double e, std::vector<std::string> &keys, std::vector<struct serialization_state> *sst, int layer, std::string layername) {
|
||||
while (pbf.next()) {
|
||||
switch (pbf.tag()) {
|
||||
case 1: {
|
||||
protozero::pbf_reader feature_reader(pbf.get_message());
|
||||
queueFeature(feature_reader, dim, e, keys, sst, layer, layername);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
pbf.skip();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parse_geobuf(std::vector<struct serialization_state> *sst, const char *src, size_t len, int layer, std::string layername) {
|
||||
protozero::pbf_reader pbf(src, len);
|
||||
|
||||
size_t dim = 2;
|
||||
double e = 1e6;
|
||||
std::vector<std::string> keys;
|
||||
|
||||
while (pbf.next()) {
|
||||
switch (pbf.tag()) {
|
||||
case 1:
|
||||
keys.push_back(pbf.get_string());
|
||||
break;
|
||||
|
||||
case 2:
|
||||
dim = pbf.get_int64();
|
||||
break;
|
||||
|
||||
case 3:
|
||||
e = pow(10, pbf.get_int64());
|
||||
break;
|
||||
|
||||
case 4: {
|
||||
protozero::pbf_reader feature_collection_reader(pbf.get_message());
|
||||
readFeatureCollection(feature_collection_reader, dim, e, keys, sst, layer, layername);
|
||||
break;
|
||||
}
|
||||
|
||||
case 5: {
|
||||
protozero::pbf_reader feature_reader(pbf.get_message());
|
||||
queueFeature(feature_reader, dim, e, keys, sst, layer, layername);
|
||||
break;
|
||||
}
|
||||
|
||||
case 6: {
|
||||
protozero::pbf_reader geometry_reader(pbf.get_message());
|
||||
std::vector<drawvec_type> dv = readGeometry(geometry_reader, dim, e, keys);
|
||||
for (size_t i = 0; i < dv.size(); i++) {
|
||||
// Always on thread 0
|
||||
outBareGeometry(dv[i].dv, dv[i].type, &(*sst)[0], layer, layername);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
pbf.skip();
|
||||
}
|
||||
}
|
||||
|
||||
runQueue();
|
||||
}
|
13
geobuf.hpp
Normal file
13
geobuf.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#ifndef GEOBUF_HPP
|
||||
#define GEOBUF_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "mbtiles.hpp"
|
||||
#include "serial.hpp"
|
||||
|
||||
void parse_geobuf(std::vector<struct serialization_state> *sst, const char *s, size_t len, int layer, std::string layername);
|
||||
|
||||
#endif
|
139
geocsv.cpp
Normal file
139
geocsv.cpp
Normal file
@ -0,0 +1,139 @@
|
||||
#include <stdlib.h>
|
||||
#include <algorithm>
|
||||
#include "geocsv.hpp"
|
||||
#include "mvt.hpp"
|
||||
#include "serial.hpp"
|
||||
#include "projection.hpp"
|
||||
#include "main.hpp"
|
||||
#include "text.hpp"
|
||||
#include "csv.hpp"
|
||||
#include "milo/dtoa_milo.h"
|
||||
#include "options.hpp"
|
||||
|
||||
void parse_geocsv(std::vector<struct serialization_state> &sst, std::string fname, int layer, std::string layername) {
|
||||
FILE *f;
|
||||
|
||||
if (fname.size() == 0) {
|
||||
f = stdin;
|
||||
} else {
|
||||
f = fopen(fname.c_str(), "r");
|
||||
if (f == NULL) {
|
||||
perror(fname.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
std::string s;
|
||||
std::vector<std::string> header;
|
||||
ssize_t latcol = -1, loncol = -1;
|
||||
|
||||
if ((s = csv_getline(f)).size() > 0) {
|
||||
std::string err = check_utf8(s);
|
||||
if (err != "") {
|
||||
fprintf(stderr, "%s: %s\n", fname.c_str(), err.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
header = csv_split(s.c_str());
|
||||
|
||||
for (size_t i = 0; i < header.size(); i++) {
|
||||
header[i] = csv_dequote(header[i]);
|
||||
|
||||
std::string lower(header[i]);
|
||||
std::transform(lower.begin(), lower.end(), lower.begin(), ::tolower);
|
||||
|
||||
if (lower == "y" || lower == "lat" || (lower.find("latitude") != std::string::npos)) {
|
||||
latcol = i;
|
||||
}
|
||||
if (lower == "x" || lower == "lon" || lower == "lng" || lower == "long" || (lower.find("longitude") != std::string::npos)) {
|
||||
loncol = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (latcol < 0 || loncol < 0) {
|
||||
fprintf(stderr, "%s: Can't find \"lat\" and \"lon\" columns\n", fname.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
size_t seq = 0;
|
||||
while ((s = csv_getline(f)).size() > 0) {
|
||||
std::string err = check_utf8(s);
|
||||
if (err != "") {
|
||||
fprintf(stderr, "%s: %s\n", fname.c_str(), err.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
seq++;
|
||||
std::vector<std::string> line = csv_split(s.c_str());
|
||||
|
||||
if (line.size() != header.size()) {
|
||||
fprintf(stderr, "%s:%zu: Mismatched column count: %zu in line, %zu in header\n", fname.c_str(), seq + 1, line.size(), header.size());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (line[loncol].empty() || line[latcol].empty()) {
|
||||
static int warned = 0;
|
||||
if (!warned) {
|
||||
fprintf(stderr, "%s:%zu: null geometry (additional not reported)\n", fname.c_str(), seq + 1);
|
||||
warned = 1;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
double lon = atof(line[loncol].c_str());
|
||||
double lat = atof(line[latcol].c_str());
|
||||
|
||||
long long x, y;
|
||||
projection->project(lon, lat, 32, &x, &y);
|
||||
drawvec dv;
|
||||
dv.push_back(draw(VT_MOVETO, x, y));
|
||||
|
||||
std::vector<std::string> full_keys;
|
||||
std::vector<serial_val> full_values;
|
||||
|
||||
for (size_t i = 0; i < line.size(); i++) {
|
||||
if (i != (size_t) latcol && i != (size_t) loncol) {
|
||||
line[i] = csv_dequote(line[i]);
|
||||
|
||||
serial_val sv;
|
||||
if (is_number(line[i])) {
|
||||
sv.type = mvt_double;
|
||||
} else if (line[i].size() == 0 && prevent[P_EMPTY_CSV_COLUMNS]) {
|
||||
sv.type = mvt_null;
|
||||
line[i] = "null";
|
||||
} else {
|
||||
sv.type = mvt_string;
|
||||
}
|
||||
sv.s = line[i];
|
||||
|
||||
full_keys.push_back(header[i]);
|
||||
full_values.push_back(sv);
|
||||
}
|
||||
}
|
||||
|
||||
serial_feature sf;
|
||||
|
||||
sf.layer = layer;
|
||||
sf.layername = layername;
|
||||
sf.segment = sst[0].segment;
|
||||
sf.has_id = false;
|
||||
sf.id = 0;
|
||||
sf.has_tippecanoe_minzoom = false;
|
||||
sf.has_tippecanoe_maxzoom = false;
|
||||
sf.feature_minzoom = false;
|
||||
sf.seq = *(sst[0].layer_seq);
|
||||
sf.geometry = dv;
|
||||
sf.t = 1; // POINT
|
||||
sf.full_keys = full_keys;
|
||||
sf.full_values = full_values;
|
||||
|
||||
serialize_feature(&sst[0], sf);
|
||||
}
|
||||
|
||||
if (fname.size() != 0) {
|
||||
if (fclose(f) != 0) {
|
||||
perror("fclose");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
13
geocsv.hpp
Normal file
13
geocsv.hpp
Normal file
@ -0,0 +1,13 @@
|
||||
#ifndef GEOCSV_HPP
|
||||
#define GEOCSV_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "mbtiles.hpp"
|
||||
#include "serial.hpp"
|
||||
|
||||
void parse_geocsv(std::vector<struct serialization_state> &sst, std::string fname, int layer, std::string layername);
|
||||
|
||||
#endif
|
184
geojson-loop.cpp
Normal file
184
geojson-loop.cpp
Normal file
@ -0,0 +1,184 @@
|
||||
#ifdef MTRACE
|
||||
#include <mcheck.h>
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include "geojson-loop.hpp"
|
||||
#include "jsonpull/jsonpull.h"
|
||||
|
||||
// XXX duplicated
|
||||
#define GEOM_TYPES 6
|
||||
static const char *geometry_names[GEOM_TYPES] = {
|
||||
"Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon",
|
||||
};
|
||||
|
||||
// XXX duplicated
|
||||
static void json_context(json_object *j) {
|
||||
char *s = json_stringify(j);
|
||||
|
||||
if (strlen(s) >= 500) {
|
||||
sprintf(s + 497, "...");
|
||||
}
|
||||
|
||||
fprintf(stderr, "In JSON object %s\n", s);
|
||||
free(s); // stringify
|
||||
}
|
||||
|
||||
void parse_json(json_feature_action *jfa, json_pull *jp) {
|
||||
long long found_hashes = 0;
|
||||
long long found_features = 0;
|
||||
long long found_geometries = 0;
|
||||
|
||||
while (1) {
|
||||
json_object *j = json_read(jp);
|
||||
if (j == NULL) {
|
||||
if (jp->error != NULL) {
|
||||
fprintf(stderr, "%s:%d: %s\n", jfa->fname.c_str(), jp->line, jp->error);
|
||||
if (jp->root != NULL) {
|
||||
json_context(jp->root);
|
||||
}
|
||||
}
|
||||
|
||||
json_free(jp->root);
|
||||
break;
|
||||
}
|
||||
|
||||
if (j->type == JSON_HASH) {
|
||||
found_hashes++;
|
||||
|
||||
if (found_hashes == 50 && found_features == 0 && found_geometries == 0) {
|
||||
fprintf(stderr, "%s:%d: Warning: not finding any GeoJSON features or geometries in input yet after 50 objects.\n", jfa->fname.c_str(), jp->line);
|
||||
}
|
||||
}
|
||||
|
||||
json_object *type = json_hash_get(j, "type");
|
||||
if (type == NULL || type->type != JSON_STRING) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (found_features == 0) {
|
||||
int i;
|
||||
int is_geometry = 0;
|
||||
for (i = 0; i < GEOM_TYPES; i++) {
|
||||
if (strcmp(type->string, geometry_names[i]) == 0) {
|
||||
is_geometry = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_geometry) {
|
||||
if (j->parent != NULL) {
|
||||
if (j->parent->type == JSON_ARRAY && j->parent->parent != NULL) {
|
||||
if (j->parent->parent->type == JSON_HASH) {
|
||||
json_object *geometries = json_hash_get(j->parent->parent, "geometries");
|
||||
if (geometries != NULL) {
|
||||
// Parent of Parent must be a GeometryCollection
|
||||
is_geometry = 0;
|
||||
}
|
||||
}
|
||||
} else if (j->parent->type == JSON_HASH) {
|
||||
json_object *geometry = json_hash_get(j->parent, "geometry");
|
||||
if (geometry != NULL) {
|
||||
// Parent must be a Feature
|
||||
is_geometry = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_geometry) {
|
||||
json_object *jo = j;
|
||||
while (jo != NULL) {
|
||||
if (jo->parent != NULL && jo->parent->type == JSON_HASH) {
|
||||
if (json_hash_get(jo->parent, "properties") == jo) {
|
||||
// Ancestor is the value corresponding to a properties key
|
||||
is_geometry = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
jo = jo->parent;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_geometry) {
|
||||
if (found_features != 0 && found_geometries == 0) {
|
||||
fprintf(stderr, "%s:%d: Warning: found a mixture of features and bare geometries\n", jfa->fname.c_str(), jp->line);
|
||||
}
|
||||
found_geometries++;
|
||||
|
||||
jfa->add_feature(j, false, NULL, NULL, NULL, j);
|
||||
json_free(j);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(type->string, "Feature") != 0) {
|
||||
if (strcmp(type->string, "FeatureCollection") == 0) {
|
||||
jfa->check_crs(j);
|
||||
json_free(j);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (found_features == 0 && found_geometries != 0) {
|
||||
fprintf(stderr, "%s:%d: Warning: found a mixture of features and bare geometries\n", jfa->fname.c_str(), jp->line);
|
||||
}
|
||||
found_features++;
|
||||
|
||||
json_object *geometry = json_hash_get(j, "geometry");
|
||||
if (geometry == NULL) {
|
||||
fprintf(stderr, "%s:%d: feature with no geometry\n", jfa->fname.c_str(), jp->line);
|
||||
json_context(j);
|
||||
json_free(j);
|
||||
continue;
|
||||
}
|
||||
|
||||
json_object *properties = json_hash_get(j, "properties");
|
||||
if (properties == NULL || (properties->type != JSON_HASH && properties->type != JSON_NULL)) {
|
||||
fprintf(stderr, "%s:%d: feature without properties hash\n", jfa->fname.c_str(), jp->line);
|
||||
json_context(j);
|
||||
json_free(j);
|
||||
continue;
|
||||
}
|
||||
|
||||
bool is_feature = true;
|
||||
{
|
||||
json_object *jo = j;
|
||||
while (jo != NULL) {
|
||||
if (jo->parent != NULL && jo->parent->type == JSON_HASH) {
|
||||
if (json_hash_get(jo->parent, "properties") == jo) {
|
||||
// Ancestor is the value corresponding to a properties key
|
||||
is_feature = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
jo = jo->parent;
|
||||
}
|
||||
}
|
||||
if (!is_feature) {
|
||||
continue;
|
||||
}
|
||||
|
||||
json_object *tippecanoe = json_hash_get(j, "tippecanoe");
|
||||
json_object *id = json_hash_get(j, "id");
|
||||
|
||||
json_object *geometries = json_hash_get(geometry, "geometries");
|
||||
if (geometries != NULL && geometries->type == JSON_ARRAY) {
|
||||
jfa->add_feature(geometries, true, properties, id, tippecanoe, j);
|
||||
} else {
|
||||
jfa->add_feature(geometry, false, properties, id, tippecanoe, j);
|
||||
}
|
||||
|
||||
json_free(j);
|
||||
|
||||
/* XXX check for any non-features in the outer object */
|
||||
}
|
||||
}
|
11
geojson-loop.hpp
Normal file
11
geojson-loop.hpp
Normal file
@ -0,0 +1,11 @@
|
||||
#include <string>
|
||||
#include "jsonpull/jsonpull.h"
|
||||
|
||||
struct json_feature_action {
|
||||
std::string fname;
|
||||
|
||||
virtual int add_feature(json_object *geometry, bool geometrycollection, json_object *properties, json_object *id, json_object *tippecanoe, json_object *feature) = 0;
|
||||
virtual void check_crs(json_object *j) = 0;
|
||||
};
|
||||
|
||||
void parse_json(json_feature_action *action, json_pull *jp);
|
578
geojson.cpp
578
geojson.cpp
@ -20,14 +20,11 @@
|
||||
#include <sys/resource.h>
|
||||
#include <pthread.h>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
extern "C" {
|
||||
#include "jsonpull/jsonpull.h"
|
||||
}
|
||||
|
||||
#include "pool.hpp"
|
||||
#include "projection.hpp"
|
||||
#include "memfile.hpp"
|
||||
@ -38,141 +35,16 @@ extern "C" {
|
||||
#include "options.hpp"
|
||||
#include "serial.hpp"
|
||||
#include "text.hpp"
|
||||
#include "read_json.hpp"
|
||||
#include "mvt.hpp"
|
||||
#include "geojson-loop.hpp"
|
||||
|
||||
#define GEOM_POINT 0 /* array of positions */
|
||||
#define GEOM_MULTIPOINT 1 /* array of arrays of positions */
|
||||
#define GEOM_LINESTRING 2 /* array of arrays of positions */
|
||||
#define GEOM_MULTILINESTRING 3 /* array of arrays of arrays of positions */
|
||||
#define GEOM_POLYGON 4 /* array of arrays of arrays of positions */
|
||||
#define GEOM_MULTIPOLYGON 5 /* array of arrays of arrays of arrays of positions */
|
||||
#define GEOM_TYPES 6
|
||||
|
||||
static const char *geometry_names[GEOM_TYPES] = {
|
||||
"Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon",
|
||||
};
|
||||
|
||||
static int geometry_within[GEOM_TYPES] = {
|
||||
-1, /* point */
|
||||
GEOM_POINT, /* multipoint */
|
||||
GEOM_POINT, /* linestring */
|
||||
GEOM_LINESTRING, /* multilinestring */
|
||||
GEOM_LINESTRING, /* polygon */
|
||||
GEOM_POLYGON, /* multipolygon */
|
||||
};
|
||||
|
||||
static int mb_geometry[GEOM_TYPES] = {
|
||||
VT_POINT, VT_POINT, VT_LINE, VT_LINE, VT_POLYGON, VT_POLYGON,
|
||||
};
|
||||
|
||||
void json_context(json_object *j) {
|
||||
char *s = json_stringify(j);
|
||||
|
||||
if (strlen(s) >= 500) {
|
||||
sprintf(s + 497, "...");
|
||||
}
|
||||
|
||||
fprintf(stderr, "In JSON object %s\n", s);
|
||||
free(s); // stringify
|
||||
}
|
||||
|
||||
long long parse_geometry(int t, json_object *j, long long *bbox, drawvec &out, int op, const char *fname, int line, int *initialized, unsigned *initial_x, unsigned *initial_y, json_object *feature) {
|
||||
long long g = 0;
|
||||
|
||||
if (j == NULL || j->type != JSON_ARRAY) {
|
||||
fprintf(stderr, "%s:%d: expected array for type %d\n", fname, line, t);
|
||||
json_context(feature);
|
||||
return g;
|
||||
}
|
||||
|
||||
int within = geometry_within[t];
|
||||
if (within >= 0) {
|
||||
size_t i;
|
||||
for (i = 0; i < j->length; i++) {
|
||||
if (within == GEOM_POINT) {
|
||||
if (i == 0 || mb_geometry[t] == GEOM_MULTIPOINT) {
|
||||
op = VT_MOVETO;
|
||||
} else {
|
||||
op = VT_LINETO;
|
||||
}
|
||||
}
|
||||
|
||||
g += parse_geometry(within, j->array[i], bbox, out, op, fname, line, initialized, initial_x, initial_y, feature);
|
||||
}
|
||||
} else {
|
||||
if (j->length >= 2 && j->array[0]->type == JSON_NUMBER && j->array[1]->type == JSON_NUMBER) {
|
||||
long long x, y;
|
||||
double lon = j->array[0]->number;
|
||||
double lat = j->array[1]->number;
|
||||
projection->project(lon, lat, 32, &x, &y);
|
||||
|
||||
if (j->length > 2) {
|
||||
static int warned = 0;
|
||||
|
||||
if (!warned) {
|
||||
fprintf(stderr, "%s:%d: ignoring dimensions beyond two\n", fname, line);
|
||||
json_context(j);
|
||||
json_context(feature);
|
||||
warned = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (x < bbox[0]) {
|
||||
bbox[0] = x;
|
||||
}
|
||||
if (y < bbox[1]) {
|
||||
bbox[1] = y;
|
||||
}
|
||||
if (x > bbox[2]) {
|
||||
bbox[2] = x;
|
||||
}
|
||||
if (y > bbox[3]) {
|
||||
bbox[3] = y;
|
||||
}
|
||||
|
||||
if (!*initialized) {
|
||||
if (x < 0 || x >= (1LL << 32) || y < 0 || y >= (1LL < 32)) {
|
||||
*initial_x = 1LL << 31;
|
||||
*initial_y = 1LL << 31;
|
||||
} else {
|
||||
*initial_x = (x >> geometry_scale) << geometry_scale;
|
||||
*initial_y = (y >> geometry_scale) << geometry_scale;
|
||||
}
|
||||
|
||||
*initialized = 1;
|
||||
}
|
||||
|
||||
draw d(op, (x >> geometry_scale), (y >> geometry_scale));
|
||||
out.push_back(d);
|
||||
g++;
|
||||
} else {
|
||||
fprintf(stderr, "%s:%d: malformed point\n", fname, line);
|
||||
json_context(j);
|
||||
json_context(feature);
|
||||
}
|
||||
}
|
||||
|
||||
if (t == GEOM_POLYGON) {
|
||||
// Note that this is not using the correct meaning of closepath.
|
||||
//
|
||||
// We are using it here to close an entire Polygon, to distinguish
|
||||
// the Polygons within a MultiPolygon from each other.
|
||||
//
|
||||
// This will be undone in fix_polygon(), which needs to know which
|
||||
// rings come from which Polygons so that it can make the winding order
|
||||
// of the outer ring be the opposite of the order of the inner rings.
|
||||
|
||||
out.push_back(draw(VT_CLOSEPATH, 0, 0));
|
||||
}
|
||||
|
||||
return g;
|
||||
}
|
||||
|
||||
int serialize_geometry(json_object *geometry, json_object *properties, json_object *id, const char *reading, int line, volatile long long *layer_seq, volatile long long *progress_seq, long long *metapos, long long *geompos, long long *indexpos, std::set<std::string> *exclude, std::set<std::string> *include, int exclude_all, FILE *metafile, FILE *geomfile, FILE *indexfile, struct memfile *poolfile, struct memfile *treefile, const char *fname, int basezoom, int layer, double droprate, long long *file_bbox, json_object *tippecanoe, int segment, int *initialized, unsigned *initial_x, unsigned *initial_y, struct reader *readers, int maxzoom, json_object *feature, std::map<std::string, layermap_entry> *layermap, std::string const &layername, bool uses_gamma) {
|
||||
int serialize_geojson_feature(struct serialization_state *sst, json_object *geometry, json_object *properties, json_object *id, int layer, json_object *tippecanoe, json_object *feature, std::string layername) {
|
||||
json_object *geometry_type = json_hash_get(geometry, "type");
|
||||
if (geometry_type == NULL) {
|
||||
static int warned = 0;
|
||||
if (!warned) {
|
||||
fprintf(stderr, "%s:%d: null geometry (additional not reported)\n", reading, line);
|
||||
fprintf(stderr, "%s:%d: null geometry (additional not reported)\n", sst->fname, sst->line);
|
||||
json_context(feature);
|
||||
warned = 1;
|
||||
}
|
||||
@ -181,14 +53,14 @@ int serialize_geometry(json_object *geometry, json_object *properties, json_obje
|
||||
}
|
||||
|
||||
if (geometry_type->type != JSON_STRING) {
|
||||
fprintf(stderr, "%s:%d: geometry type is not a string\n", reading, line);
|
||||
fprintf(stderr, "%s:%d: geometry type is not a string\n", sst->fname, sst->line);
|
||||
json_context(feature);
|
||||
return 0;
|
||||
}
|
||||
|
||||
json_object *coordinates = json_hash_get(geometry, "coordinates");
|
||||
if (coordinates == NULL || coordinates->type != JSON_ARRAY) {
|
||||
fprintf(stderr, "%s:%d: feature without coordinates array\n", reading, line);
|
||||
fprintf(stderr, "%s:%d: feature without coordinates array\n", sst->fname, sst->line);
|
||||
json_context(feature);
|
||||
return 0;
|
||||
}
|
||||
@ -200,7 +72,7 @@ int serialize_geometry(json_object *geometry, json_object *properties, json_obje
|
||||
}
|
||||
}
|
||||
if (t >= GEOM_TYPES) {
|
||||
fprintf(stderr, "%s:%d: Can't handle geometry type %s\n", reading, line, geometry_type->string);
|
||||
fprintf(stderr, "%s:%d: Can't handle geometry type %s\n", sst->fname, sst->line, geometry_type->string);
|
||||
json_context(feature);
|
||||
return 0;
|
||||
}
|
||||
@ -211,19 +83,13 @@ int serialize_geometry(json_object *geometry, json_object *properties, json_obje
|
||||
|
||||
if (tippecanoe != NULL) {
|
||||
json_object *min = json_hash_get(tippecanoe, "minzoom");
|
||||
if (min != NULL && min->type == JSON_NUMBER) {
|
||||
tippecanoe_minzoom = min->number;
|
||||
}
|
||||
if (min != NULL && min->type == JSON_STRING) {
|
||||
tippecanoe_minzoom = atoi(min->string);
|
||||
if (min != NULL && (min->type == JSON_STRING || min->type == JSON_NUMBER)) {
|
||||
tippecanoe_minzoom = integer_zoom(sst->fname, min->string);
|
||||
}
|
||||
|
||||
json_object *max = json_hash_get(tippecanoe, "maxzoom");
|
||||
if (max != NULL && max->type == JSON_NUMBER) {
|
||||
tippecanoe_maxzoom = max->number;
|
||||
}
|
||||
if (max != NULL && max->type == JSON_STRING) {
|
||||
tippecanoe_maxzoom = atoi(max->string);
|
||||
if (max != NULL && (max->type == JSON_STRING || max->type == JSON_NUMBER)) {
|
||||
tippecanoe_maxzoom = integer_zoom(sst->fname, max->string);
|
||||
}
|
||||
|
||||
json_object *ln = json_hash_get(tippecanoe, "layer");
|
||||
@ -241,162 +107,114 @@ int serialize_geometry(json_object *geometry, json_object *properties, json_obje
|
||||
id_value = strtoull(id->string, &err, 10);
|
||||
|
||||
if (err != NULL && *err != '\0') {
|
||||
fprintf(stderr, "Warning: Can't represent non-integer feature ID %s\n", id->string);
|
||||
static bool warned_frac = false;
|
||||
|
||||
if (!warned_frac) {
|
||||
fprintf(stderr, "Warning: Can't represent non-integer feature ID %s\n", id->string);
|
||||
warned_frac = true;
|
||||
}
|
||||
} else if (std::to_string(id_value) != id->string) {
|
||||
static bool warned = false;
|
||||
|
||||
if (!warned) {
|
||||
fprintf(stderr, "Warning: Can't represent too-large feature ID %s\n", id->string);
|
||||
warned = true;
|
||||
}
|
||||
} else {
|
||||
has_id = true;
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Warning: Can't represent negative feature ID %s\n", id->string);
|
||||
static bool warned_neg = false;
|
||||
|
||||
if (!warned_neg) {
|
||||
fprintf(stderr, "Warning: Can't represent negative feature ID %s\n", id->string);
|
||||
warned_neg = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
char *s = json_stringify(id);
|
||||
fprintf(stderr, "Warning: Can't represent non-numeric feature ID %s\n", s);
|
||||
free(s); // stringify
|
||||
bool converted = false;
|
||||
|
||||
if (additional[A_CONVERT_NUMERIC_IDS] && id->type == JSON_STRING) {
|
||||
char *err = NULL;
|
||||
id_value = strtoull(id->string, &err, 10);
|
||||
|
||||
if (err != NULL && *err != '\0') {
|
||||
static bool warned_frac = false;
|
||||
|
||||
if (!warned_frac) {
|
||||
fprintf(stderr, "Warning: Can't represent non-integer feature ID %s\n", id->string);
|
||||
warned_frac = true;
|
||||
}
|
||||
} else if (std::to_string(id_value) != id->string) {
|
||||
static bool warned = false;
|
||||
|
||||
if (!warned) {
|
||||
fprintf(stderr, "Warning: Can't represent too-large feature ID %s\n", id->string);
|
||||
warned = true;
|
||||
}
|
||||
} else {
|
||||
has_id = true;
|
||||
converted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!converted) {
|
||||
static bool warned_nan = false;
|
||||
|
||||
if (!warned_nan) {
|
||||
char *s = json_stringify(id);
|
||||
fprintf(stderr, "Warning: Can't represent non-numeric feature ID %s\n", s);
|
||||
free(s); // stringify
|
||||
warned_nan = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long long bbox[] = {LLONG_MAX, LLONG_MAX, LLONG_MIN, LLONG_MIN};
|
||||
|
||||
size_t nprop = 0;
|
||||
if (properties != NULL && properties->type == JSON_HASH) {
|
||||
nprop = properties->length;
|
||||
}
|
||||
|
||||
char *metakey[nprop];
|
||||
std::vector<char *> metakey;
|
||||
metakey.resize(nprop);
|
||||
|
||||
std::vector<std::string> metaval;
|
||||
metaval.resize(nprop);
|
||||
int metatype[nprop];
|
||||
|
||||
std::vector<int> metatype;
|
||||
metatype.resize(nprop);
|
||||
|
||||
size_t m = 0;
|
||||
|
||||
for (size_t i = 0; i < nprop; i++) {
|
||||
if (properties->keys[i]->type == JSON_STRING) {
|
||||
std::string s(properties->keys[i]->string);
|
||||
|
||||
if (exclude_all) {
|
||||
if (include->count(s) == 0) {
|
||||
continue;
|
||||
}
|
||||
} else if (exclude->count(s) != 0) {
|
||||
continue;
|
||||
}
|
||||
int type = -1;
|
||||
std::string val;
|
||||
stringify_value(properties->values[i], type, val, sst->fname, sst->line, feature);
|
||||
|
||||
type_and_string tas;
|
||||
tas.string = s;
|
||||
tas.type = -1;
|
||||
|
||||
metakey[m] = properties->keys[i]->string;
|
||||
|
||||
if (properties->values[i] != NULL && properties->values[i]->type == JSON_STRING) {
|
||||
tas.type = metatype[m] = VT_STRING;
|
||||
metaval[m] = std::string(properties->values[i]->string);
|
||||
std::string err = check_utf8(metaval[m]);
|
||||
if (err != "") {
|
||||
fprintf(stderr, "%s:%d: %s\n", reading, line, err.c_str());
|
||||
json_context(feature);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (type >= 0) {
|
||||
metakey[m] = properties->keys[i]->string;
|
||||
metatype[m] = type;
|
||||
metaval[m] = val;
|
||||
m++;
|
||||
} else if (properties->values[i] != NULL && properties->values[i]->type == JSON_NUMBER) {
|
||||
tas.type = metatype[m] = VT_NUMBER;
|
||||
metaval[m] = std::string(properties->values[i]->string);
|
||||
m++;
|
||||
} else if (properties->values[i] != NULL && (properties->values[i]->type == JSON_TRUE || properties->values[i]->type == JSON_FALSE)) {
|
||||
tas.type = metatype[m] = VT_BOOLEAN;
|
||||
metaval[m] = std::string(properties->values[i]->type == JSON_TRUE ? "true" : "false");
|
||||
m++;
|
||||
} else if (properties->values[i] != NULL && (properties->values[i]->type == JSON_NULL)) {
|
||||
;
|
||||
} else {
|
||||
tas.type = metatype[m] = VT_STRING;
|
||||
const char *v = json_stringify(properties->values[i]);
|
||||
metaval[m] = std::string(v);
|
||||
free((void *) v); // stringify
|
||||
metakey[m] = properties->keys[i]->string;
|
||||
metatype[m] = mvt_null;
|
||||
metaval[m] = "null";
|
||||
m++;
|
||||
}
|
||||
|
||||
if (tas.type >= 0) {
|
||||
auto fk = layermap->find(layername);
|
||||
fk->second.file_keys.insert(tas);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
drawvec dv;
|
||||
long long g = parse_geometry(t, coordinates, bbox, dv, VT_MOVETO, fname, line, initialized, initial_x, initial_y, feature);
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
dv = fix_polygon(dv);
|
||||
}
|
||||
|
||||
bool inline_meta = true;
|
||||
// Don't inline metadata for features that will span several tiles at maxzoom
|
||||
if (g > 0 && (bbox[2] < bbox[0] || bbox[3] < bbox[1])) {
|
||||
fprintf(stderr, "Internal error: impossible feature bounding box %llx,%llx,%llx,%llx\n", bbox[0], bbox[1], bbox[2], bbox[3]);
|
||||
}
|
||||
if (bbox[2] - bbox[0] > (2LL << (32 - maxzoom)) || bbox[3] - bbox[1] > (2LL << (32 - maxzoom))) {
|
||||
inline_meta = false;
|
||||
|
||||
if (prevent[P_CLIPPING]) {
|
||||
static volatile long long warned = 0;
|
||||
long long extent = ((bbox[2] - bbox[0]) / ((1LL << (32 - maxzoom)) + 1)) * ((bbox[3] - bbox[1]) / ((1LL << (32 - maxzoom)) + 1));
|
||||
if (extent > warned) {
|
||||
fprintf(stderr, "Warning: %s:%d: Large unclipped (-pc) feature may be duplicated across %lld tiles\n", fname, line, extent);
|
||||
warned = extent;
|
||||
|
||||
if (extent > 10000) {
|
||||
fprintf(stderr, "Exiting because this can't be right.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double extent = 0;
|
||||
if (additional[A_DROP_SMALLEST_AS_NEEDED]) {
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
for (size_t i = 0; i < dv.size(); i++) {
|
||||
if (dv[i].op == VT_MOVETO) {
|
||||
size_t j;
|
||||
for (j = i + 1; j < dv.size(); j++) {
|
||||
if (dv[j].op != VT_LINETO) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extent += get_area(dv, i, j);
|
||||
i = j - 1;
|
||||
}
|
||||
}
|
||||
} else if (mb_geometry[t] == VT_LINE) {
|
||||
for (size_t i = 1; i < dv.size(); i++) {
|
||||
if (dv[i].op == VT_LINETO) {
|
||||
double xd = dv[i].x - dv[i - 1].x;
|
||||
double yd = dv[i].y - dv[i - 1].y;
|
||||
extent += sqrt(xd * xd + yd * yd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tippecanoe_layername.size() != 0) {
|
||||
if (layermap->count(tippecanoe_layername) == 0) {
|
||||
layermap->insert(std::pair<std::string, layermap_entry>(tippecanoe_layername, layermap_entry(layermap->size())));
|
||||
}
|
||||
|
||||
auto ai = layermap->find(tippecanoe_layername);
|
||||
if (ai != layermap->end()) {
|
||||
layer = ai->second.id;
|
||||
} else {
|
||||
fprintf(stderr, "Internal error: can't find layer name %s\n", tippecanoe_layername.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
long long geomstart = *geompos;
|
||||
long long bbox_index;
|
||||
parse_geometry(t, coordinates, dv, VT_MOVETO, sst->fname, sst->line, feature);
|
||||
|
||||
serial_feature sf;
|
||||
sf.layer = layer;
|
||||
sf.segment = segment;
|
||||
sf.segment = sst->segment;
|
||||
sf.t = mb_geometry[t];
|
||||
sf.has_id = has_id;
|
||||
sf.id = id_value;
|
||||
@ -405,74 +223,26 @@ int serialize_geometry(json_object *geometry, json_object *properties, json_obje
|
||||
sf.has_tippecanoe_maxzoom = (tippecanoe_maxzoom != -1);
|
||||
sf.tippecanoe_maxzoom = tippecanoe_maxzoom;
|
||||
sf.geometry = dv;
|
||||
sf.m = m;
|
||||
sf.feature_minzoom = 0; // Will be filled in during index merging
|
||||
sf.extent = (long long) extent;
|
||||
sf.seq = *(sst->layer_seq);
|
||||
|
||||
if (prevent[P_INPUT_ORDER]) {
|
||||
sf.seq = *layer_seq;
|
||||
if (tippecanoe_layername.size() != 0) {
|
||||
sf.layername = tippecanoe_layername;
|
||||
} else {
|
||||
sf.seq = 0;
|
||||
sf.layername = layername;
|
||||
}
|
||||
|
||||
// Calculate the center even if off the edge of the plane,
|
||||
// and then mask to bring it back into the addressable area
|
||||
long long midx = (bbox[0] / 2 + bbox[2] / 2) & ((1LL << 32) - 1);
|
||||
long long midy = (bbox[1] / 2 + bbox[3] / 2) & ((1LL << 32) - 1);
|
||||
bbox_index = encode(midx, midy);
|
||||
for (size_t i = 0; i < m; i++) {
|
||||
sf.full_keys.push_back(metakey[i]);
|
||||
|
||||
if (additional[A_DROP_DENSEST_AS_NEEDED] || additional[A_CALCULATE_FEATURE_DENSITY] || additional[A_INCREASE_GAMMA_AS_NEEDED] || uses_gamma) {
|
||||
sf.index = bbox_index;
|
||||
serial_val sv;
|
||||
sv.type = metatype[i];
|
||||
sv.s = metaval[i];
|
||||
|
||||
sf.full_values.push_back(sv);
|
||||
}
|
||||
|
||||
if (inline_meta) {
|
||||
sf.metapos = -1;
|
||||
for (size_t i = 0; i < m; i++) {
|
||||
sf.keys.push_back(addpool(poolfile, treefile, metakey[i], VT_STRING));
|
||||
sf.values.push_back(addpool(poolfile, treefile, metaval[i].c_str(), metatype[i]));
|
||||
}
|
||||
} else {
|
||||
sf.metapos = *metapos;
|
||||
for (size_t i = 0; i < m; i++) {
|
||||
serialize_long_long(metafile, addpool(poolfile, treefile, metakey[i], VT_STRING), metapos, fname);
|
||||
serialize_long_long(metafile, addpool(poolfile, treefile, metaval[i].c_str(), metatype[i]), metapos, fname);
|
||||
}
|
||||
}
|
||||
|
||||
serialize_feature(geomfile, &sf, geompos, fname, *initial_x >> geometry_scale, *initial_y >> geometry_scale, false);
|
||||
|
||||
struct index index;
|
||||
index.start = geomstart;
|
||||
index.end = *geompos;
|
||||
index.segment = segment;
|
||||
index.seq = *layer_seq;
|
||||
index.t = sf.t;
|
||||
index.index = bbox_index;
|
||||
|
||||
fwrite_check(&index, sizeof(struct index), 1, indexfile, fname);
|
||||
*indexpos += sizeof(struct index);
|
||||
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
if (bbox[i] < file_bbox[i]) {
|
||||
file_bbox[i] = bbox[i];
|
||||
}
|
||||
}
|
||||
for (size_t i = 2; i < 4; i++) {
|
||||
if (bbox[i] > file_bbox[i]) {
|
||||
file_bbox[i] = bbox[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (*progress_seq % 10000 == 0) {
|
||||
checkdisk(readers, CPUS);
|
||||
if (!quiet) {
|
||||
fprintf(stderr, "Read %.2f million features\r", *progress_seq / 1000000.0);
|
||||
}
|
||||
}
|
||||
(*progress_seq)++;
|
||||
(*layer_seq)++;
|
||||
|
||||
return 1;
|
||||
return serialize_feature(sst, sf);
|
||||
}
|
||||
|
||||
void check_crs(json_object *j, const char *reading) {
|
||||
@ -481,141 +251,55 @@ void check_crs(json_object *j, const char *reading) {
|
||||
json_object *properties = json_hash_get(crs, "properties");
|
||||
if (properties != NULL) {
|
||||
json_object *name = json_hash_get(properties, "name");
|
||||
if (name->type == JSON_STRING) {
|
||||
if (name != NULL && name->type == JSON_STRING) {
|
||||
if (strcmp(name->string, projection->alias) != 0) {
|
||||
fprintf(stderr, "%s: Warning: GeoJSON specified projection \"%s\", not \"%s\".\n", reading, name->string, projection->alias);
|
||||
if (!quiet) {
|
||||
fprintf(stderr, "%s: Warning: GeoJSON specified projection \"%s\", not the expected \"%s\".\n", reading, name->string, projection->alias);
|
||||
fprintf(stderr, "%s: If \"%s\" is not the expected projection, use -s to specify the right one.\n", reading, projection->alias);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void parse_json(json_pull *jp, const char *reading, volatile long long *layer_seq, volatile long long *progress_seq, long long *metapos, long long *geompos, long long *indexpos, std::set<std::string> *exclude, std::set<std::string> *include, int exclude_all, FILE *metafile, FILE *geomfile, FILE *indexfile, struct memfile *poolfile, struct memfile *treefile, char *fname, int basezoom, int layer, double droprate, long long *file_bbox, int segment, int *initialized, unsigned *initial_x, unsigned *initial_y, struct reader *readers, int maxzoom, std::map<std::string, layermap_entry> *layermap, std::string layername, bool uses_gamma) {
|
||||
long long found_hashes = 0;
|
||||
long long found_features = 0;
|
||||
long long found_geometries = 0;
|
||||
struct json_serialize_action : json_feature_action {
|
||||
serialization_state *sst;
|
||||
int layer;
|
||||
std::string layername;
|
||||
|
||||
while (1) {
|
||||
json_object *j = json_read(jp);
|
||||
if (j == NULL) {
|
||||
if (jp->error != NULL) {
|
||||
fprintf(stderr, "%s:%d: %s\n", reading, jp->line, jp->error);
|
||||
if (jp->root != NULL) {
|
||||
json_context(jp->root);
|
||||
}
|
||||
}
|
||||
|
||||
json_free(jp->root);
|
||||
break;
|
||||
}
|
||||
|
||||
if (j->type == JSON_HASH) {
|
||||
found_hashes++;
|
||||
|
||||
if (found_hashes == 50 && found_features == 0 && found_geometries == 0) {
|
||||
fprintf(stderr, "%s:%d: Warning: not finding any GeoJSON features or geometries in input yet after 50 objects.\n", reading, jp->line);
|
||||
}
|
||||
}
|
||||
|
||||
json_object *type = json_hash_get(j, "type");
|
||||
if (type == NULL || type->type != JSON_STRING) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (found_features == 0) {
|
||||
int i;
|
||||
int is_geometry = 0;
|
||||
for (i = 0; i < GEOM_TYPES; i++) {
|
||||
if (strcmp(type->string, geometry_names[i]) == 0) {
|
||||
is_geometry = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_geometry) {
|
||||
if (j->parent != NULL) {
|
||||
if (j->parent->type == JSON_ARRAY) {
|
||||
if (j->parent->parent->type == JSON_HASH) {
|
||||
json_object *geometries = json_hash_get(j->parent->parent, "geometries");
|
||||
if (geometries != NULL) {
|
||||
// Parent of Parent must be a GeometryCollection
|
||||
is_geometry = 0;
|
||||
}
|
||||
}
|
||||
} else if (j->parent->type == JSON_HASH) {
|
||||
json_object *geometry = json_hash_get(j->parent, "geometry");
|
||||
if (geometry != NULL) {
|
||||
// Parent must be a Feature
|
||||
is_geometry = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_geometry) {
|
||||
if (found_features != 0 && found_geometries == 0) {
|
||||
fprintf(stderr, "%s:%d: Warning: found a mixture of features and bare geometries\n", reading, jp->line);
|
||||
}
|
||||
found_geometries++;
|
||||
|
||||
serialize_geometry(j, NULL, NULL, reading, jp->line, layer_seq, progress_seq, metapos, geompos, indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, basezoom, layer, droprate, file_bbox, NULL, segment, initialized, initial_x, initial_y, readers, maxzoom, j, layermap, layername, uses_gamma);
|
||||
json_free(j);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (strcmp(type->string, "Feature") != 0) {
|
||||
if (strcmp(type->string, "FeatureCollection") == 0) {
|
||||
check_crs(j, reading);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (found_features == 0 && found_geometries != 0) {
|
||||
fprintf(stderr, "%s:%d: Warning: found a mixture of features and bare geometries\n", reading, jp->line);
|
||||
}
|
||||
found_features++;
|
||||
|
||||
json_object *geometry = json_hash_get(j, "geometry");
|
||||
if (geometry == NULL) {
|
||||
fprintf(stderr, "%s:%d: feature with no geometry\n", reading, jp->line);
|
||||
json_context(j);
|
||||
json_free(j);
|
||||
continue;
|
||||
}
|
||||
|
||||
json_object *properties = json_hash_get(j, "properties");
|
||||
if (properties == NULL || (properties->type != JSON_HASH && properties->type != JSON_NULL)) {
|
||||
fprintf(stderr, "%s:%d: feature without properties hash\n", reading, jp->line);
|
||||
json_context(j);
|
||||
json_free(j);
|
||||
continue;
|
||||
}
|
||||
|
||||
json_object *tippecanoe = json_hash_get(j, "tippecanoe");
|
||||
json_object *id = json_hash_get(j, "id");
|
||||
|
||||
json_object *geometries = json_hash_get(geometry, "geometries");
|
||||
if (geometries != NULL) {
|
||||
size_t g;
|
||||
for (g = 0; g < geometries->length; g++) {
|
||||
serialize_geometry(geometries->array[g], properties, id, reading, jp->line, layer_seq, progress_seq, metapos, geompos, indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, basezoom, layer, droprate, file_bbox, tippecanoe, segment, initialized, initial_x, initial_y, readers, maxzoom, j, layermap, layername, uses_gamma);
|
||||
int add_feature(json_object *geometry, bool geometrycollection, json_object *properties, json_object *id, json_object *tippecanoe, json_object *feature) {
|
||||
sst->line = geometry->parser->line;
|
||||
if (geometrycollection) {
|
||||
int ret = 1;
|
||||
for (size_t g = 0; g < geometry->length; g++) {
|
||||
ret &= serialize_geojson_feature(sst, geometry->array[g], properties, id, layer, tippecanoe, feature, layername);
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
serialize_geometry(geometry, properties, id, reading, jp->line, layer_seq, progress_seq, metapos, geompos, indexpos, exclude, include, exclude_all, metafile, geomfile, indexfile, poolfile, treefile, fname, basezoom, layer, droprate, file_bbox, tippecanoe, segment, initialized, initial_x, initial_y, readers, maxzoom, j, layermap, layername, uses_gamma);
|
||||
return serialize_geojson_feature(sst, geometry, properties, id, layer, tippecanoe, feature, layername);
|
||||
}
|
||||
|
||||
json_free(j);
|
||||
|
||||
/* XXX check for any non-features in the outer object */
|
||||
}
|
||||
|
||||
void check_crs(json_object *j) {
|
||||
::check_crs(j, fname.c_str());
|
||||
}
|
||||
};
|
||||
|
||||
void parse_json(struct serialization_state *sst, json_pull *jp, int layer, std::string layername) {
|
||||
json_serialize_action jsa;
|
||||
jsa.fname = sst->fname;
|
||||
jsa.sst = sst;
|
||||
jsa.layer = layer;
|
||||
jsa.layername = layername;
|
||||
|
||||
parse_json(&jsa, jp);
|
||||
}
|
||||
|
||||
void *run_parse_json(void *v) {
|
||||
struct parse_json_args *pja = (struct parse_json_args *) v;
|
||||
|
||||
parse_json(pja->jp, pja->reading, pja->layer_seq, pja->progress_seq, pja->metapos, pja->geompos, pja->indexpos, pja->exclude, pja->include, pja->exclude_all, pja->metafile, pja->geomfile, pja->indexfile, pja->poolfile, pja->treefile, pja->fname, pja->basezoom, pja->layer, pja->droprate, pja->file_bbox, pja->segment, pja->initialized, pja->initial_x, pja->initial_y, pja->readers, pja->maxzoom, pja->layermap, *pja->layername, pja->uses_gamma);
|
||||
parse_json(pja->sst, pja->jp, pja->layer, *pja->layername);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
47
geojson.hpp
47
geojson.hpp
@ -1,37 +1,30 @@
|
||||
#ifndef GEOJSON_HPP
|
||||
#define GEOJSON_HPP
|
||||
|
||||
#include <stdio.h>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include "mbtiles.hpp"
|
||||
#include "jsonpull/jsonpull.h"
|
||||
#include "serial.hpp"
|
||||
|
||||
struct parse_json_args {
|
||||
json_pull *jp;
|
||||
const char *reading;
|
||||
volatile long long *layer_seq;
|
||||
volatile long long *progress_seq;
|
||||
long long *metapos;
|
||||
long long *geompos;
|
||||
long long *indexpos;
|
||||
std::set<std::string> *exclude;
|
||||
std::set<std::string> *include;
|
||||
int exclude_all;
|
||||
FILE *metafile;
|
||||
FILE *geomfile;
|
||||
FILE *indexfile;
|
||||
struct memfile *poolfile;
|
||||
struct memfile *treefile;
|
||||
char *fname;
|
||||
int basezoom;
|
||||
int layer;
|
||||
double droprate;
|
||||
long long *file_bbox;
|
||||
int segment;
|
||||
int *initialized;
|
||||
unsigned *initial_x;
|
||||
unsigned *initial_y;
|
||||
struct reader *readers;
|
||||
int maxzoom;
|
||||
std::map<std::string, layermap_entry> *layermap;
|
||||
std::string *layername;
|
||||
bool uses_gamma;
|
||||
|
||||
struct serialization_state *sst;
|
||||
|
||||
parse_json_args(json_pull *jp1, int layer1, std::string *layername1, struct serialization_state *sst1)
|
||||
: jp(jp1), layer(layer1), layername(layername1), sst(sst1) {
|
||||
}
|
||||
};
|
||||
|
||||
struct json_pull *json_begin_map(char *map, long long len);
|
||||
void json_end_map(struct json_pull *jp);
|
||||
|
||||
void parse_json(json_pull *jp, const char *reading, volatile long long *layer_seq, volatile long long *progress_seq, long long *metapos, long long *geompos, long long *indexpos, std::set<std::string> *exclude, std::set<std::string> *include, int exclude_all, FILE *metafile, FILE *geomfile, FILE *indexfile, struct memfile *poolfile, struct memfile *treefile, char *fname, int basezoom, int layer, double droprate, long long *file_bbox, int segment, int *initialized, unsigned *initial_x, unsigned *initial_y, struct reader *readers, int maxzoom, std::map<std::string, layermap_entry> *layermap, std::string layername, bool uses_gamma);
|
||||
void parse_json(struct serialization_state *sst, json_pull *jp, int layer, std::string layername);
|
||||
void *run_parse_json(void *v);
|
||||
|
||||
#endif
|
||||
|
275
geometry.cpp
275
geometry.cpp
@ -3,23 +3,28 @@
|
||||
#include <string>
|
||||
#include <stack>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <unistd.h>
|
||||
#include <cmath>
|
||||
#include <limits.h>
|
||||
#include <sqlite3.h>
|
||||
#include <mapbox/geometry.hpp>
|
||||
#include <mapbox/geometry/point.hpp>
|
||||
#include <mapbox/geometry/multi_polygon.hpp>
|
||||
#include <mapbox/geometry/wagyu/wagyu.hpp>
|
||||
#include <mapbox/geometry/wagyu/quick_clip.hpp>
|
||||
#include <mapbox/geometry/snap_rounding.hpp>
|
||||
#include "geometry.hpp"
|
||||
#include "projection.hpp"
|
||||
#include "serial.hpp"
|
||||
#include "main.hpp"
|
||||
#include "options.hpp"
|
||||
|
||||
static int pnpoly(drawvec &vert, size_t start, size_t nvert, long long testx, long long testy);
|
||||
static int clip(double *x0, double *y0, double *x1, double *y1, double xmin, double ymin, double xmax, double ymax);
|
||||
|
||||
drawvec decode_geometry(FILE *meta, long long *geompos, int z, unsigned tx, unsigned ty, int detail, long long *bbox, unsigned initial_x, unsigned initial_y) {
|
||||
drawvec decode_geometry(FILE *meta, std::atomic<long long> *geompos, int z, unsigned tx, unsigned ty, long long *bbox, unsigned initial_x, unsigned initial_y) {
|
||||
drawvec out;
|
||||
|
||||
bbox[0] = LLONG_MAX;
|
||||
@ -164,6 +169,27 @@ double get_area(drawvec &geom, size_t i, size_t j) {
|
||||
return area;
|
||||
}
|
||||
|
||||
double get_mp_area(drawvec &geom) {
|
||||
double ret = 0;
|
||||
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
if (geom[i].op == VT_MOVETO) {
|
||||
size_t j;
|
||||
|
||||
for (j = i + 1; j < geom.size(); j++) {
|
||||
if (geom[j].op != VT_LINETO) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ret += get_area(geom, i, j);
|
||||
i = j - 1;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void decode_clipped(mapbox::geometry::multi_polygon<long long> &t, drawvec &out) {
|
||||
out.clear();
|
||||
|
||||
@ -194,7 +220,7 @@ static void decode_clipped(mapbox::geometry::multi_polygon<long long> &t, drawve
|
||||
}
|
||||
}
|
||||
|
||||
drawvec clean_or_clip_poly(drawvec &geom, int z, int detail, int buffer, bool clip) {
|
||||
drawvec clean_or_clip_poly(drawvec &geom, int z, int buffer, bool clip) {
|
||||
mapbox::geometry::wagyu::wagyu<long long> wagyu;
|
||||
|
||||
geom = remove_noop(geom, VT_POLYGON, 0);
|
||||
@ -244,7 +270,7 @@ drawvec clean_or_clip_poly(drawvec &geom, int z, int detail, int buffer, bool cl
|
||||
mapbox::geometry::multi_polygon<long long> result;
|
||||
try {
|
||||
wagyu.execute(mapbox::geometry::wagyu::clip_type_union, result, mapbox::geometry::wagyu::fill_type_positive, mapbox::geometry::wagyu::fill_type_positive);
|
||||
} catch (std::runtime_error e) {
|
||||
} catch (std::runtime_error &e) {
|
||||
FILE *f = fopen("/tmp/wagyu.log", "w");
|
||||
fprintf(f, "%s\n", e.what());
|
||||
fprintf(stderr, "%s\n", e.what());
|
||||
@ -320,30 +346,42 @@ static int pnpoly(drawvec &vert, size_t start, size_t nvert, long long testx, lo
|
||||
return c;
|
||||
}
|
||||
|
||||
void check_polygon(drawvec &geom, drawvec &before) {
|
||||
for (size_t i = 0; i + 1 < geom.size(); i++) {
|
||||
for (size_t j = i + 1; j + 1 < geom.size(); j++) {
|
||||
if (geom[i + 1].op == VT_LINETO && geom[j + 1].op == VT_LINETO) {
|
||||
double s1_x = geom[i + 1].x - geom[i + 0].x;
|
||||
double s1_y = geom[i + 1].y - geom[i + 0].y;
|
||||
double s2_x = geom[j + 1].x - geom[j + 0].x;
|
||||
double s2_y = geom[j + 1].y - geom[j + 0].y;
|
||||
void check_polygon(drawvec &geom) {
|
||||
geom = remove_noop(geom, VT_POLYGON, 0);
|
||||
|
||||
double s, t;
|
||||
s = (-s1_y * (geom[i + 0].x - geom[j + 0].x) + s1_x * (geom[i + 0].y - geom[j + 0].y)) / (-s2_x * s1_y + s1_x * s2_y);
|
||||
t = (s2_x * (geom[i + 0].y - geom[j + 0].y) - s2_y * (geom[i + 0].x - geom[j + 0].x)) / (-s2_x * s1_y + s1_x * s2_y);
|
||||
|
||||
if (t > 0 && t < 1 && s > 0 && s < 1) {
|
||||
printf("Internal error: self-intersecting polygon. %lld,%lld to %lld,%lld intersects %lld,%lld to %lld,%lld\n",
|
||||
geom[i + 0].x, geom[i + 0].y,
|
||||
geom[i + 1].x, geom[i + 1].y,
|
||||
geom[j + 0].x, geom[j + 0].y,
|
||||
geom[j + 1].x, geom[j + 1].y);
|
||||
mapbox::geometry::multi_polygon<long long> mp;
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
if (geom[i].op == VT_MOVETO) {
|
||||
size_t j;
|
||||
for (j = i + 1; j < geom.size(); j++) {
|
||||
if (geom[j].op != VT_LINETO) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (j >= i + 4) {
|
||||
mapbox::geometry::linear_ring<long long> lr;
|
||||
|
||||
for (size_t k = i; k < j; k++) {
|
||||
lr.push_back(mapbox::geometry::point<long long>(geom[k].x, geom[k].y));
|
||||
}
|
||||
|
||||
if (lr.size() >= 3) {
|
||||
mapbox::geometry::polygon<long long> p;
|
||||
p.push_back(lr);
|
||||
mp.push_back(p);
|
||||
}
|
||||
}
|
||||
|
||||
i = j - 1;
|
||||
}
|
||||
}
|
||||
|
||||
mapbox::geometry::multi_polygon<long long> mp2 = mapbox::geometry::snap_round(mp, true, true);
|
||||
if (mp != mp2) {
|
||||
fprintf(stderr, "Internal error: self-intersecting polygon\n");
|
||||
}
|
||||
|
||||
size_t outer_start = -1;
|
||||
size_t outer_len = 0;
|
||||
|
||||
@ -423,103 +461,13 @@ drawvec close_poly(drawvec &geom) {
|
||||
return out;
|
||||
}
|
||||
|
||||
static bool inside(draw d, int edge, long long minx, long long miny, long long maxx, long long maxy) {
|
||||
switch (edge) {
|
||||
case 0: // top
|
||||
return d.y > miny;
|
||||
|
||||
case 1: // right
|
||||
return d.x < maxx;
|
||||
|
||||
case 2: // bottom
|
||||
return d.y < maxy;
|
||||
|
||||
case 3: // left
|
||||
return d.x > minx;
|
||||
}
|
||||
|
||||
fprintf(stderr, "internal error inside\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
static draw intersect(draw a, draw b, int edge, long long minx, long long miny, long long maxx, long long maxy) {
|
||||
// The casts to double are because the product of coordinates
|
||||
// can overflow a long long if the tile buffer is large.
|
||||
|
||||
switch (edge) {
|
||||
case 0: // top
|
||||
return draw(VT_LINETO, a.x + (double) (b.x - a.x) * (miny - a.y) / (b.y - a.y), miny);
|
||||
|
||||
case 1: // right
|
||||
return draw(VT_LINETO, maxx, a.y + (double) (b.y - a.y) * (maxx - a.x) / (b.x - a.x));
|
||||
|
||||
case 2: // bottom
|
||||
return draw(VT_LINETO, a.x + (double) (b.x - a.x) * (maxy - a.y) / (b.y - a.y), maxy);
|
||||
|
||||
case 3: // left
|
||||
return draw(VT_LINETO, minx, a.y + (double) (b.y - a.y) * (minx - a.x) / (b.x - a.x));
|
||||
}
|
||||
|
||||
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, long long minx, long long miny, long long maxx, long long maxy) {
|
||||
drawvec out = geom;
|
||||
|
||||
for (int edge = 0; edge < 4; edge++) {
|
||||
if (out.size() > 0) {
|
||||
drawvec in = out;
|
||||
out.resize(0);
|
||||
|
||||
draw S = in[in.size() - 1];
|
||||
|
||||
for (size_t e = 0; e < in.size(); e++) {
|
||||
draw E = in[e];
|
||||
|
||||
if (inside(E, edge, minx, miny, maxx, maxy)) {
|
||||
if (!inside(S, edge, minx, miny, maxx, maxy)) {
|
||||
out.push_back(intersect(S, E, edge, minx, miny, maxx, maxy));
|
||||
}
|
||||
out.push_back(E);
|
||||
} else if (inside(S, edge, minx, miny, maxx, maxy)) {
|
||||
out.push_back(intersect(S, E, edge, minx, miny, maxx, maxy));
|
||||
}
|
||||
|
||||
S = E;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (out.size() > 0) {
|
||||
// If the polygon begins and ends outside the edge,
|
||||
// the starting and ending points will be left as the
|
||||
// places where it intersects the edge. Need to add
|
||||
// another point to close the loop.
|
||||
|
||||
if (out[0].x != out[out.size() - 1].x || out[0].y != out[out.size() - 1].y) {
|
||||
out.push_back(out[0]);
|
||||
}
|
||||
|
||||
if (out.size() < 3) {
|
||||
// fprintf(stderr, "Polygon degenerated to a line segment\n");
|
||||
out.clear();
|
||||
return out;
|
||||
}
|
||||
|
||||
out[0].op = VT_MOVETO;
|
||||
for (size_t i = 1; i < out.size(); i++) {
|
||||
out[i].op = VT_LINETO;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
drawvec simple_clip_poly(drawvec &geom, long long minx, long long miny, long long maxx, long long maxy) {
|
||||
drawvec out;
|
||||
|
||||
mapbox::geometry::point<long long> min(minx, miny);
|
||||
mapbox::geometry::point<long long> max(maxx, maxy);
|
||||
mapbox::geometry::box<long long> bbox(min, max);
|
||||
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
if (geom[i].op == VT_MOVETO) {
|
||||
size_t j;
|
||||
@ -529,19 +477,25 @@ drawvec simple_clip_poly(drawvec &geom, long long minx, long long miny, long lon
|
||||
}
|
||||
}
|
||||
|
||||
drawvec tmp;
|
||||
mapbox::geometry::linear_ring<long long> ring;
|
||||
for (size_t k = i; k < j; k++) {
|
||||
tmp.push_back(geom[k]);
|
||||
ring.push_back(mapbox::geometry::point<long long>(geom[k].x, geom[k].y));
|
||||
}
|
||||
tmp = clip_poly1(tmp, minx, miny, maxx, maxy);
|
||||
if (tmp.size() > 0) {
|
||||
if (tmp[0].x != tmp[tmp.size() - 1].x || tmp[0].y != tmp[tmp.size() - 1].y) {
|
||||
fprintf(stderr, "Internal error: Polygon ring not closed\n");
|
||||
exit(EXIT_FAILURE);
|
||||
|
||||
mapbox::geometry::linear_ring<long long> lr = mapbox::geometry::wagyu::quick_clip::quick_lr_clip(ring, bbox);
|
||||
|
||||
if (lr.size() > 0) {
|
||||
for (size_t k = 0; k < lr.size(); k++) {
|
||||
if (k == 0) {
|
||||
out.push_back(draw(VT_MOVETO, lr[k].x, lr[k].y));
|
||||
} else {
|
||||
out.push_back(draw(VT_LINETO, lr[k].x, lr[k].y));
|
||||
}
|
||||
}
|
||||
|
||||
if (lr.size() > 0 && lr[0] != lr[lr.size() - 1]) {
|
||||
out.push_back(draw(VT_LINETO, lr[0].x, lr[0].y));
|
||||
}
|
||||
}
|
||||
for (size_t k = 0; k < tmp.size(); k++) {
|
||||
out.push_back(tmp[k]);
|
||||
}
|
||||
|
||||
i = j - 1;
|
||||
@ -554,7 +508,7 @@ drawvec simple_clip_poly(drawvec &geom, long long minx, long long miny, long lon
|
||||
return out;
|
||||
}
|
||||
|
||||
drawvec simple_clip_poly(drawvec &geom, int z, int detail, int buffer) {
|
||||
drawvec simple_clip_poly(drawvec &geom, int z, int buffer) {
|
||||
long long area = 1LL << (32 - z);
|
||||
long long clip_buffer = buffer * area / 256;
|
||||
|
||||
@ -640,17 +594,21 @@ 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;
|
||||
|
||||
drawvec clip_point(drawvec &geom, int z, long long buffer) {
|
||||
long long min = 0;
|
||||
long long area = 1LL << (32 - z);
|
||||
|
||||
min -= buffer * area / 256;
|
||||
area += buffer * area / 256;
|
||||
|
||||
return clip_point(geom, min, min, area, area);
|
||||
}
|
||||
|
||||
drawvec clip_point(drawvec &geom, long long minx, long long miny, long long maxx, long long maxy) {
|
||||
drawvec out;
|
||||
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
if (geom[i].x >= min && geom[i].y >= min && geom[i].x <= area && geom[i].y <= area) {
|
||||
if (geom[i].x >= minx && geom[i].y >= miny && geom[i].x <= maxx && geom[i].y <= maxy) {
|
||||
out.push_back(geom[i]);
|
||||
}
|
||||
}
|
||||
@ -658,7 +616,7 @@ drawvec clip_point(drawvec &geom, int z, int detail, long long buffer) {
|
||||
return out;
|
||||
}
|
||||
|
||||
int quick_check(long long *bbox, int z, int detail, long long buffer) {
|
||||
int quick_check(long long *bbox, int z, long long buffer) {
|
||||
long long min = 0;
|
||||
long long area = 1LL << (32 - z);
|
||||
|
||||
@ -682,7 +640,7 @@ int quick_check(long long *bbox, int z, int detail, long long buffer) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
bool point_within_tile(long long x, long long y, int z, int detail, long long buffer) {
|
||||
bool point_within_tile(long long x, long long y, int z) {
|
||||
// No adjustment for buffer, because the point must be
|
||||
// strictly within the tile to appear exactly once
|
||||
|
||||
@ -691,14 +649,18 @@ bool point_within_tile(long long x, long long y, int z, int detail, long long bu
|
||||
return x >= 0 && y >= 0 && x < area && y < area;
|
||||
}
|
||||
|
||||
drawvec clip_lines(drawvec &geom, int z, int detail, long long buffer) {
|
||||
drawvec out;
|
||||
|
||||
drawvec clip_lines(drawvec &geom, int z, long long buffer) {
|
||||
long long min = 0;
|
||||
long long area = 1LL << (32 - z);
|
||||
min -= buffer * area / 256;
|
||||
area += buffer * area / 256;
|
||||
|
||||
return clip_lines(geom, min, min, area, area);
|
||||
}
|
||||
|
||||
drawvec clip_lines(drawvec &geom, long long minx, long long miny, long long maxx, long long maxy) {
|
||||
drawvec out;
|
||||
|
||||
for (size_t 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;
|
||||
@ -707,7 +669,7 @@ drawvec clip_lines(drawvec &geom, int z, int detail, long long buffer) {
|
||||
double x2 = geom[i - 0].x;
|
||||
double y2 = geom[i - 0].y;
|
||||
|
||||
int c = clip(&x1, &y1, &x2, &y2, min, min, area, area);
|
||||
int c = clip(&x1, &y1, &x2, &y2, minx, miny, maxx, maxy);
|
||||
|
||||
if (c > 1) { // clipped
|
||||
out.push_back(draw(VT_MOVETO, x1, y1));
|
||||
@ -840,7 +802,7 @@ drawvec impose_tile_boundaries(drawvec &geom, long long extent) {
|
||||
return out;
|
||||
}
|
||||
|
||||
drawvec simplify_lines(drawvec &geom, int z, int detail, bool mark_tile_bounds, double simplification, size_t retain) {
|
||||
drawvec simplify_lines(drawvec &geom, int z, int detail, bool mark_tile_bounds, double simplification, size_t retain, drawvec const &shared_nodes) {
|
||||
int res = 1 << (32 - detail - z);
|
||||
long long area = 1LL << (32 - z);
|
||||
|
||||
@ -852,6 +814,13 @@ drawvec simplify_lines(drawvec &geom, int z, int detail, bool mark_tile_bounds,
|
||||
} else {
|
||||
geom[i].necessary = 1;
|
||||
}
|
||||
|
||||
if (prevent[P_SIMPLIFY_SHARED_NODES]) {
|
||||
auto pt = std::lower_bound(shared_nodes.begin(), shared_nodes.end(), geom[i]);
|
||||
if (pt != shared_nodes.end() && *pt == geom[i]) {
|
||||
geom[i].necessary = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mark_tile_bounds) {
|
||||
@ -912,8 +881,8 @@ drawvec reorder_lines(drawvec &geom) {
|
||||
// instead of down and to the right
|
||||
// so that it will coalesce better
|
||||
|
||||
unsigned long long l1 = encode(geom[0].x, geom[0].y);
|
||||
unsigned long long l2 = encode(geom[geom.size() - 1].x, geom[geom.size() - 1].y);
|
||||
unsigned long long l1 = encode_index(geom[0].x, geom[0].y);
|
||||
unsigned long long l2 = encode_index(geom[geom.size() - 1].x, geom[geom.size() - 1].y);
|
||||
|
||||
if (l1 > l2) {
|
||||
drawvec out;
|
||||
@ -959,8 +928,21 @@ drawvec fix_polygon(drawvec &geom) {
|
||||
// Reverse ring if winding order doesn't match
|
||||
// inner/outer expectation
|
||||
|
||||
double area = get_area(ring, 0, ring.size());
|
||||
if ((area > 0) != outer) {
|
||||
bool reverse_ring = false;
|
||||
if (prevent[P_USE_SOURCE_POLYGON_WINDING]) {
|
||||
// GeoJSON winding is reversed from vector winding
|
||||
reverse_ring = true;
|
||||
} else if (prevent[P_REVERSE_SOURCE_POLYGON_WINDING]) {
|
||||
// GeoJSON winding is reversed from vector winding
|
||||
reverse_ring = false;
|
||||
} else {
|
||||
double area = get_area(ring, 0, ring.size());
|
||||
if ((area > 0) != outer) {
|
||||
reverse_ring = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (reverse_ring) {
|
||||
drawvec tmp;
|
||||
for (int a = ring.size() - 1; a >= 0; a--) {
|
||||
tmp.push_back(ring[a]);
|
||||
@ -1160,14 +1142,14 @@ drawvec stairstep(drawvec &geom, int z, int detail) {
|
||||
double scale = 1 << (32 - detail - z);
|
||||
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
geom[i].x = std::round(geom[i].x / scale);
|
||||
geom[i].y = std::round(geom[i].y / scale);
|
||||
geom[i].x = std::floor(geom[i].x / scale);
|
||||
geom[i].y = std::floor(geom[i].y / scale);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
if (geom[i].op == VT_MOVETO) {
|
||||
out.push_back(geom[i]);
|
||||
} else {
|
||||
} else if (out.size() > 0) {
|
||||
long long x0 = out[out.size() - 1].x;
|
||||
long long y0 = out[out.size() - 1].y;
|
||||
long long x1 = geom[i].x;
|
||||
@ -1227,6 +1209,9 @@ drawvec stairstep(drawvec &geom, int z, int detail) {
|
||||
}
|
||||
|
||||
// out.push_back(draw(VT_LINETO, xx, yy));
|
||||
} else {
|
||||
fprintf(stderr, "Can't happen: stairstepping lineto with no moveto\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
|
56
geometry.hpp
56
geometry.hpp
@ -1,3 +1,10 @@
|
||||
#ifndef GEOMETRY_HPP
|
||||
#define GEOMETRY_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <sqlite3.h>
|
||||
|
||||
#define VT_POINT 1
|
||||
#define VT_LINE 2
|
||||
#define VT_POLYGON 3
|
||||
@ -7,10 +14,6 @@
|
||||
#define VT_LINETO 2
|
||||
#define VT_CLOSEPATH 7
|
||||
|
||||
#define VT_STRING 1
|
||||
#define VT_NUMBER 2
|
||||
#define VT_BOOLEAN 7
|
||||
|
||||
// The bitfield is to make sizeof(draw) be 16 instead of 24
|
||||
// at the cost, apparently, of a 0.7% increase in running time
|
||||
// for packing and unpacking.
|
||||
@ -20,18 +23,18 @@ struct draw {
|
||||
long long y : 40;
|
||||
signed char necessary;
|
||||
|
||||
draw(int nop, long long nx, long long ny) {
|
||||
this->op = nop;
|
||||
this->x = nx;
|
||||
this->y = ny;
|
||||
this->necessary = 0;
|
||||
draw(int nop, long long nx, long long ny)
|
||||
: x(nx),
|
||||
op(nop),
|
||||
y(ny),
|
||||
necessary(0) {
|
||||
}
|
||||
|
||||
draw() {
|
||||
this->op = 0;
|
||||
this->x = 0;
|
||||
this->y = 0;
|
||||
this->necessary = 0;
|
||||
draw()
|
||||
: x(0),
|
||||
op(0),
|
||||
y(0),
|
||||
necessary(0) {
|
||||
}
|
||||
|
||||
bool operator<(draw const &s) const {
|
||||
@ -53,21 +56,28 @@ struct draw {
|
||||
|
||||
typedef std::vector<draw> drawvec;
|
||||
|
||||
drawvec decode_geometry(FILE *meta, long long *geompos, int z, unsigned tx, unsigned ty, int detail, long long *bbox, unsigned initial_x, unsigned initial_y);
|
||||
drawvec decode_geometry(FILE *meta, std::atomic<long long> *geompos, int z, unsigned tx, unsigned ty, long long *bbox, unsigned initial_x, unsigned initial_y);
|
||||
void to_tile_scale(drawvec &geom, int z, int detail);
|
||||
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 simple_clip_poly(drawvec &geom, int z, int detail, int buffer);
|
||||
drawvec clip_point(drawvec &geom, int z, long long buffer);
|
||||
drawvec clean_or_clip_poly(drawvec &geom, int z, int buffer, bool clip);
|
||||
drawvec simple_clip_poly(drawvec &geom, int z, int buffer);
|
||||
drawvec close_poly(drawvec &geom);
|
||||
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);
|
||||
drawvec clip_lines(drawvec &geom, int z, long long buffer);
|
||||
drawvec stairstep(drawvec &geom, int z, int detail);
|
||||
bool point_within_tile(long long x, long long y, 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, bool mark_tile_bounds, double simplification, size_t retain);
|
||||
bool point_within_tile(long long x, long long y, int z);
|
||||
int quick_check(long long *bbox, int z, long long buffer);
|
||||
drawvec simplify_lines(drawvec &geom, int z, int detail, bool mark_tile_bounds, double simplification, size_t retain, drawvec const &shared_nodes);
|
||||
drawvec reorder_lines(drawvec &geom);
|
||||
drawvec fix_polygon(drawvec &geom);
|
||||
std::vector<drawvec> chop_polygon(std::vector<drawvec> &geoms);
|
||||
void check_polygon(drawvec &geom, drawvec &before);
|
||||
void check_polygon(drawvec &geom);
|
||||
double get_area(drawvec &geom, size_t i, size_t j);
|
||||
double get_mp_area(drawvec &geom);
|
||||
|
||||
drawvec simple_clip_poly(drawvec &geom, long long x1, long long y1, long long x2, long long y2);
|
||||
drawvec clip_lines(drawvec &geom, long long x1, long long y1, long long x2, long long y2);
|
||||
drawvec clip_point(drawvec &geom, long long x1, long long y1, long long x2, long long y2);
|
||||
|
||||
#endif
|
||||
|
@ -36,14 +36,14 @@ json_pull *json_begin(ssize_t (*read)(struct json_pull *, char *buffer, size_t n
|
||||
|
||||
static inline int peek(json_pull *j) {
|
||||
if (j->buffer_head < j->buffer_tail) {
|
||||
return j->buffer[j->buffer_head];
|
||||
return (unsigned char) j->buffer[j->buffer_head];
|
||||
} else {
|
||||
j->buffer_head = 0;
|
||||
j->buffer_tail = j->read(j, j->buffer, BUFFER);
|
||||
if (j->buffer_head >= j->buffer_tail) {
|
||||
return EOF;
|
||||
}
|
||||
return j->buffer[j->buffer_head];
|
||||
return (unsigned char) j->buffer[j->buffer_head];
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ json_pull *json_begin_file(FILE *f) {
|
||||
}
|
||||
|
||||
static ssize_t read_string(json_pull *j, char *buffer, size_t n) {
|
||||
char *cp = j->source;
|
||||
const char *cp = j->source;
|
||||
size_t out = 0;
|
||||
|
||||
while (out < n && cp[out] != '\0') {
|
||||
@ -77,12 +77,12 @@ static ssize_t read_string(json_pull *j, char *buffer, size_t n) {
|
||||
out++;
|
||||
}
|
||||
|
||||
j->source = cp + out;
|
||||
j->source = (void *) (cp + out);
|
||||
return out;
|
||||
}
|
||||
|
||||
json_pull *json_begin_string(char *s) {
|
||||
return json_begin(read_string, s);
|
||||
json_pull *json_begin_string(const char *s) {
|
||||
return json_begin(read_string, (void *) s);
|
||||
}
|
||||
|
||||
void json_end(json_pull *p) {
|
||||
@ -295,7 +295,23 @@ again:
|
||||
|
||||
return NULL;
|
||||
}
|
||||
} while (c == ' ' || c == '\t' || c == '\r' || c == '\n');
|
||||
|
||||
// Byte-order mark
|
||||
if (c == 0xEF) {
|
||||
int c2 = peek(j);
|
||||
if (c2 == 0xBB) {
|
||||
c2 = read_wrap(j);
|
||||
c2 = peek(j);
|
||||
if (c2 == 0xBF) {
|
||||
c2 = read_wrap(j);
|
||||
c = ' ';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
j->error = "Corrupt byte-order mark found";
|
||||
return NULL;
|
||||
}
|
||||
} while (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == 0x1E);
|
||||
|
||||
/////////////////////////// Arrays
|
||||
|
||||
@ -384,6 +400,32 @@ again:
|
||||
return add_object(j, JSON_NULL);
|
||||
}
|
||||
|
||||
/////////////////////////// NaN
|
||||
|
||||
if (c == 'N') {
|
||||
if (read_wrap(j) != 'a' || read_wrap(j) != 'N') {
|
||||
j->error = "Found misspelling of NaN";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
j->error = "JSON does not allow NaN";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/////////////////////////// Infinity
|
||||
|
||||
if (c == 'I') {
|
||||
if (read_wrap(j) != 'n' || read_wrap(j) != 'f' || read_wrap(j) != 'i' ||
|
||||
read_wrap(j) != 'n' || read_wrap(j) != 'i' || read_wrap(j) != 't' ||
|
||||
read_wrap(j) != 'y') {
|
||||
j->error = "Found misspelling of Infinity";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
j->error = "JSON does not allow Infinity";
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/////////////////////////// True
|
||||
|
||||
if (c == 't') {
|
||||
@ -527,29 +569,21 @@ again:
|
||||
struct string val;
|
||||
string_init(&val);
|
||||
|
||||
int surrogate = -1;
|
||||
while ((c = read_wrap(j)) != EOF) {
|
||||
if (c == '"') {
|
||||
if (surrogate >= 0) {
|
||||
string_append(&val, 0xE0 | (surrogate >> 12));
|
||||
string_append(&val, 0x80 | ((surrogate >> 6) & 0x3F));
|
||||
string_append(&val, 0x80 | (surrogate & 0x3F));
|
||||
surrogate = -1;
|
||||
}
|
||||
|
||||
break;
|
||||
} else if (c == '\\') {
|
||||
c = read_wrap(j);
|
||||
|
||||
if (c == '"') {
|
||||
string_append(&val, '"');
|
||||
} else if (c == '\\') {
|
||||
string_append(&val, '\\');
|
||||
} else if (c == '/') {
|
||||
string_append(&val, '/');
|
||||
} else if (c == 'b') {
|
||||
string_append(&val, '\b');
|
||||
} else if (c == 'f') {
|
||||
string_append(&val, '\f');
|
||||
} else if (c == 'n') {
|
||||
string_append(&val, '\n');
|
||||
} else if (c == 'r') {
|
||||
string_append(&val, '\r');
|
||||
} else if (c == 't') {
|
||||
string_append(&val, '\t');
|
||||
} else if (c == 'u') {
|
||||
if (c == 'u') {
|
||||
char hex[5] = "aaaa";
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
@ -560,27 +594,93 @@ again:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long ch = strtoul(hex, NULL, 16);
|
||||
if (ch >= 0xd800 && ch <= 0xdbff) {
|
||||
if (surrogate < 0) {
|
||||
surrogate = ch;
|
||||
} else {
|
||||
// Impossible surrogate, so output the first half,
|
||||
// keep what might be a legitimate new first half.
|
||||
string_append(&val, 0xE0 | (surrogate >> 12));
|
||||
string_append(&val, 0x80 | ((surrogate >> 6) & 0x3F));
|
||||
string_append(&val, 0x80 | (surrogate & 0x3F));
|
||||
surrogate = ch;
|
||||
}
|
||||
continue;
|
||||
} else if (ch >= 0xdc00 && c <= 0xdfff) {
|
||||
if (surrogate >= 0) {
|
||||
long c1 = surrogate - 0xd800;
|
||||
long c2 = ch - 0xdc00;
|
||||
ch = ((c1 << 10) | c2) + 0x010000;
|
||||
surrogate = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (surrogate >= 0) {
|
||||
string_append(&val, 0xE0 | (surrogate >> 12));
|
||||
string_append(&val, 0x80 | ((surrogate >> 6) & 0x3F));
|
||||
string_append(&val, 0x80 | (surrogate & 0x3F));
|
||||
surrogate = -1;
|
||||
}
|
||||
|
||||
if (ch <= 0x7F) {
|
||||
string_append(&val, ch);
|
||||
} else if (ch <= 0x7FF) {
|
||||
string_append(&val, 0xC0 | (ch >> 6));
|
||||
string_append(&val, 0x80 | (ch & 0x3F));
|
||||
} else {
|
||||
} else if (ch < 0xFFFF) {
|
||||
string_append(&val, 0xE0 | (ch >> 12));
|
||||
string_append(&val, 0x80 | ((ch >> 6) & 0x3F));
|
||||
string_append(&val, 0x80 | (ch & 0x3F));
|
||||
} else {
|
||||
string_append(&val, 0xF0 | (ch >> 18));
|
||||
string_append(&val, 0x80 | ((ch >> 12) & 0x3F));
|
||||
string_append(&val, 0x80 | ((ch >> 6) & 0x3F));
|
||||
string_append(&val, 0x80 | (ch & 0x3F));
|
||||
}
|
||||
} else {
|
||||
j->error = "Found backslash followed by unknown character";
|
||||
string_free(&val);
|
||||
return NULL;
|
||||
if (surrogate >= 0) {
|
||||
string_append(&val, 0xE0 | (surrogate >> 12));
|
||||
string_append(&val, 0x80 | ((surrogate >> 6) & 0x3F));
|
||||
string_append(&val, 0x80 | (surrogate & 0x3F));
|
||||
surrogate = -1;
|
||||
}
|
||||
|
||||
if (c == '"') {
|
||||
string_append(&val, '"');
|
||||
} else if (c == '\\') {
|
||||
string_append(&val, '\\');
|
||||
} else if (c == '/') {
|
||||
string_append(&val, '/');
|
||||
} else if (c == 'b') {
|
||||
string_append(&val, '\b');
|
||||
} else if (c == 'f') {
|
||||
string_append(&val, '\f');
|
||||
} else if (c == 'n') {
|
||||
string_append(&val, '\n');
|
||||
} else if (c == 'r') {
|
||||
string_append(&val, '\r');
|
||||
} else if (c == 't') {
|
||||
string_append(&val, '\t');
|
||||
} else {
|
||||
j->error = "Found backslash followed by unknown character";
|
||||
string_free(&val);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
} else if (c < ' ') {
|
||||
j->error = "Found control character in string";
|
||||
string_free(&val);
|
||||
return NULL;
|
||||
} else {
|
||||
if (surrogate >= 0) {
|
||||
string_append(&val, 0xE0 | (surrogate >> 12));
|
||||
string_append(&val, 0x80 | ((surrogate >> 6) & 0x3F));
|
||||
string_append(&val, 0x80 | (surrogate & 0x3F));
|
||||
surrogate = -1;
|
||||
}
|
||||
|
||||
string_append(&val, c);
|
||||
}
|
||||
}
|
||||
@ -666,6 +766,23 @@ void json_free(json_object *o) {
|
||||
free(o);
|
||||
}
|
||||
|
||||
static void json_disconnect_parser(json_object *o) {
|
||||
if (o->type == JSON_HASH) {
|
||||
size_t i;
|
||||
for (i = 0; i < o->length; i++) {
|
||||
json_disconnect_parser(o->keys[i]);
|
||||
json_disconnect_parser(o->values[i]);
|
||||
}
|
||||
} else if (o->type == JSON_ARRAY) {
|
||||
size_t i;
|
||||
for (i = 0; i < o->length; i++) {
|
||||
json_disconnect_parser(o->array[i]);
|
||||
}
|
||||
}
|
||||
|
||||
o->parser = NULL;
|
||||
}
|
||||
|
||||
void json_disconnect(json_object *o) {
|
||||
// Expunge references to this as an array element
|
||||
// or a hash key or value.
|
||||
@ -719,12 +836,13 @@ void json_disconnect(json_object *o) {
|
||||
o->parser->root = NULL;
|
||||
}
|
||||
|
||||
json_disconnect_parser(o);
|
||||
o->parent = NULL;
|
||||
}
|
||||
|
||||
static void json_print_one(struct string *val, json_object *o) {
|
||||
if (o == NULL) {
|
||||
string_append_string(val, "NULL");
|
||||
string_append_string(val, "...");
|
||||
} else if (o->type == JSON_STRING) {
|
||||
string_append(val, '\"');
|
||||
|
||||
@ -763,7 +881,7 @@ static void json_print_one(struct string *val, json_object *o) {
|
||||
static void json_print(struct string *val, json_object *o) {
|
||||
if (o == NULL) {
|
||||
// Hash value in incompletely read hash
|
||||
string_append_string(val, "NULL");
|
||||
string_append_string(val, "...");
|
||||
} else if (o->type == JSON_HASH) {
|
||||
string_append(val, '{');
|
||||
|
||||
|
@ -1,3 +1,10 @@
|
||||
#ifndef JSONPULL_H
|
||||
#define JSONPULL_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef enum json_type {
|
||||
// These types can be returned by json_read()
|
||||
JSON_HASH,
|
||||
@ -49,7 +56,7 @@ typedef struct json_pull {
|
||||
} json_pull;
|
||||
|
||||
json_pull *json_begin_file(FILE *f);
|
||||
json_pull *json_begin_string(char *s);
|
||||
json_pull *json_begin_string(const char *s);
|
||||
|
||||
json_pull *json_begin(ssize_t (*read)(struct json_pull *, char *buffer, size_t n), void *source);
|
||||
void json_end(json_pull *p);
|
||||
@ -65,3 +72,9 @@ void json_disconnect(json_object *j);
|
||||
json_object *json_hash_get(json_object *o, const char *s);
|
||||
|
||||
char *json_stringify(json_object *o);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
499
jsontool.cpp
Normal file
499
jsontool.cpp
Normal file
@ -0,0 +1,499 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
#include <stdarg.h>
|
||||
#include <unistd.h>
|
||||
#include <string>
|
||||
#include <getopt.h>
|
||||
#include <vector>
|
||||
#include "jsonpull/jsonpull.h"
|
||||
#include "csv.hpp"
|
||||
#include "text.hpp"
|
||||
#include "geojson-loop.hpp"
|
||||
|
||||
int fail = EXIT_SUCCESS;
|
||||
bool wrap = false;
|
||||
const char *extract = NULL;
|
||||
|
||||
FILE *csvfile = NULL;
|
||||
std::vector<std::string> header;
|
||||
std::vector<std::string> fields;
|
||||
int pe = false;
|
||||
|
||||
std::string buffered;
|
||||
int buffered_type = -1;
|
||||
// 0: nothing yet
|
||||
// 1: buffered a line
|
||||
// 2: wrote the line and the wrapper
|
||||
int buffer_state = 0;
|
||||
|
||||
std::vector<unsigned long> decode32(const char *s) {
|
||||
std::vector<unsigned long> utf32;
|
||||
|
||||
while (*s != '\0') {
|
||||
unsigned long b = *(s++) & 0xFF;
|
||||
|
||||
if (b < 0x80) {
|
||||
utf32.push_back(b);
|
||||
} else if ((b & 0xe0) == 0xc0) {
|
||||
unsigned long c = (b & 0x1f) << 6;
|
||||
unsigned long b1 = *(s++) & 0xFF;
|
||||
|
||||
if ((b1 & 0xc0) == 0x80) {
|
||||
c |= b1 & 0x3f;
|
||||
utf32.push_back(c);
|
||||
} else {
|
||||
s--;
|
||||
utf32.push_back(0xfffd);
|
||||
}
|
||||
} else if ((b & 0xf0) == 0xe0) {
|
||||
unsigned long c = (b & 0x0f) << 12;
|
||||
unsigned long b1 = *(s++) & 0xFF;
|
||||
|
||||
if ((b1 & 0xc0) == 0x80) {
|
||||
c |= (b1 & 0x3f) << 6;
|
||||
unsigned long b2 = *(s++) & 0xFF;
|
||||
|
||||
if ((b2 & 0xc0) == 0x80) {
|
||||
c |= b2 & 0x3f;
|
||||
utf32.push_back(c);
|
||||
} else {
|
||||
s -= 2;
|
||||
utf32.push_back(0xfffd);
|
||||
}
|
||||
} else {
|
||||
s--;
|
||||
utf32.push_back(0xfffd);
|
||||
}
|
||||
} else if ((b & 0xf8) == 0xf0) {
|
||||
unsigned long c = (b & 0x07) << 18;
|
||||
unsigned long b1 = *(s++) & 0xFF;
|
||||
|
||||
if ((b1 & 0xc0) == 0x80) {
|
||||
c |= (b1 & 0x3f) << 12;
|
||||
unsigned long b2 = *(s++) & 0xFF;
|
||||
|
||||
if ((b2 & 0xc0) == 0x80) {
|
||||
c |= (b2 & 0x3f) << 6;
|
||||
unsigned long b3 = *(s++) & 0xFF;
|
||||
|
||||
if ((b3 & 0xc0) == 0x80) {
|
||||
c |= b3 & 0x3f;
|
||||
|
||||
utf32.push_back(c);
|
||||
} else {
|
||||
s -= 3;
|
||||
utf32.push_back(0xfffd);
|
||||
}
|
||||
} else {
|
||||
s -= 2;
|
||||
utf32.push_back(0xfffd);
|
||||
}
|
||||
} else {
|
||||
s -= 1;
|
||||
utf32.push_back(0xfffd);
|
||||
}
|
||||
} else {
|
||||
utf32.push_back(0xfffd);
|
||||
}
|
||||
}
|
||||
|
||||
return utf32;
|
||||
}
|
||||
|
||||
// This uses a really weird encoding for strings
|
||||
// so that they will sort in UTF-32 order in spite of quoting
|
||||
|
||||
std::string sort_quote(const char *s) {
|
||||
std::vector<unsigned long> utf32 = decode32(s);
|
||||
std::string ret;
|
||||
|
||||
for (size_t i = 0; i < utf32.size(); i++) {
|
||||
if (utf32[i] < 0xD800) {
|
||||
char buf[8];
|
||||
sprintf(buf, "\\u%04lu", utf32[i]);
|
||||
ret.append(std::string(buf));
|
||||
} else {
|
||||
unsigned long c = utf32[i];
|
||||
|
||||
if (c <= 0x7f) {
|
||||
ret.push_back(c);
|
||||
} else if (c <= 0x7ff) {
|
||||
ret.push_back(0xc0 | (c >> 6));
|
||||
ret.push_back(0x80 | (c & 0x3f));
|
||||
} else if (c <= 0xffff) {
|
||||
ret.push_back(0xe0 | (c >> 12));
|
||||
ret.push_back(0x80 | ((c >> 6) & 0x3f));
|
||||
ret.push_back(0x80 | (c & 0x3f));
|
||||
} else {
|
||||
ret.push_back(0xf0 | (c >> 18));
|
||||
ret.push_back(0x80 | ((c >> 12) & 0x3f));
|
||||
ret.push_back(0x80 | ((c >> 6) & 0x3f));
|
||||
ret.push_back(0x80 | (c & 0x3f));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void out(std::string const &s, int type, json_object *properties) {
|
||||
if (extract != NULL) {
|
||||
std::string extracted = sort_quote("null");
|
||||
bool found = false;
|
||||
|
||||
json_object *o = json_hash_get(properties, extract);
|
||||
if (o != NULL) {
|
||||
found = true;
|
||||
if (o->type == JSON_STRING || o->type == JSON_NUMBER) {
|
||||
extracted = sort_quote(o->string);
|
||||
} else {
|
||||
// Don't really know what to do about sort quoting
|
||||
// for arbitrary objects
|
||||
|
||||
const char *out = json_stringify(o);
|
||||
extracted = sort_quote(out);
|
||||
free((void *) out);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
fprintf(stderr, "Warning: extract key \"%s\" not found in JSON\n", extract);
|
||||
warned = true;
|
||||
}
|
||||
}
|
||||
|
||||
printf("{\"%s\":%s}\n", extracted.c_str(), s.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!wrap) {
|
||||
printf("%s\n", s.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer_state == 0) {
|
||||
buffered = s;
|
||||
buffered_type = type;
|
||||
buffer_state = 1;
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer_state == 1) {
|
||||
if (buffered_type == 1) {
|
||||
printf("{\"type\":\"FeatureCollection\",\"features\":[\n");
|
||||
} else {
|
||||
printf("{\"type\":\"GeometryCollection\",\"geometries\":[\n");
|
||||
}
|
||||
|
||||
printf("%s\n", buffered.c_str());
|
||||
buffer_state = 2;
|
||||
}
|
||||
|
||||
printf(",\n%s\n", s.c_str());
|
||||
|
||||
if (type != buffered_type) {
|
||||
fprintf(stderr, "Error: mix of bare geometries and features\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
std::string prev_joinkey;
|
||||
|
||||
void join_csv(json_object *j) {
|
||||
if (header.size() == 0) {
|
||||
std::string s = csv_getline(csvfile);
|
||||
if (s.size() == 0) {
|
||||
fprintf(stderr, "Couldn't get column header from CSV file\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::string err = check_utf8(s);
|
||||
if (err != "") {
|
||||
fprintf(stderr, "%s\n", err.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
header = csv_split(s.c_str());
|
||||
|
||||
for (size_t i = 0; i < header.size(); i++) {
|
||||
header[i] = csv_dequote(header[i]);
|
||||
}
|
||||
|
||||
if (header.size() == 0) {
|
||||
fprintf(stderr, "No columns in CSV header \"%s\"\n", s.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
json_object *properties = json_hash_get(j, "properties");
|
||||
json_object *key = NULL;
|
||||
|
||||
if (properties != NULL) {
|
||||
key = json_hash_get(properties, header[0].c_str());
|
||||
}
|
||||
|
||||
if (key == NULL) {
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
fprintf(stderr, "Warning: couldn't find CSV key \"%s\" in JSON\n", header[0].c_str());
|
||||
warned = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::string joinkey;
|
||||
if (key->type == JSON_STRING || key->type == JSON_NUMBER) {
|
||||
joinkey = key->string;
|
||||
} else {
|
||||
const char *s = json_stringify(key);
|
||||
joinkey = s;
|
||||
free((void *) s);
|
||||
}
|
||||
|
||||
if (joinkey < prev_joinkey) {
|
||||
fprintf(stderr, "GeoJSON file is out of sort: \"%s\" follows \"%s\"\n", joinkey.c_str(), prev_joinkey.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
prev_joinkey = joinkey;
|
||||
|
||||
if (fields.size() == 0 || joinkey > fields[0]) {
|
||||
std::string prevkey;
|
||||
if (fields.size() > 0) {
|
||||
prevkey = fields[0];
|
||||
}
|
||||
|
||||
while (true) {
|
||||
std::string s = csv_getline(csvfile);
|
||||
if (s.size() == 0) {
|
||||
fields.clear();
|
||||
break;
|
||||
}
|
||||
|
||||
std::string err = check_utf8(s);
|
||||
if (err != "") {
|
||||
fprintf(stderr, "%s\n", err.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
fields = csv_split(s.c_str());
|
||||
|
||||
for (size_t i = 0; i < fields.size(); i++) {
|
||||
fields[i] = csv_dequote(fields[i]);
|
||||
}
|
||||
|
||||
if (fields.size() > 0 && fields[0] < prevkey) {
|
||||
fprintf(stderr, "CSV file is out of sort: \"%s\" follows \"%s\"\n", fields[0].c_str(), prevkey.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (fields.size() > 0 && fields[0] >= joinkey) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (fields.size() > 0) {
|
||||
prevkey = fields[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (fields.size() > 0 && joinkey == fields[0]) {
|
||||
// This knows more about the structure of JSON objects than it ought to
|
||||
properties->keys = (json_object **) realloc((void *) properties->keys, (properties->length + 32 + fields.size()) * sizeof(json_object *));
|
||||
properties->values = (json_object **) realloc((void *) properties->values, (properties->length + 32 + fields.size()) * sizeof(json_object *));
|
||||
if (properties->keys == NULL || properties->values == NULL) {
|
||||
perror("realloc");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
for (size_t i = 1; i < fields.size(); i++) {
|
||||
std::string k = header[i];
|
||||
std::string v = fields[i];
|
||||
json_type attr_type = JSON_STRING;
|
||||
|
||||
if (v.size() > 0) {
|
||||
if (v[0] == '"') {
|
||||
v = csv_dequote(v);
|
||||
} else if (is_number(v)) {
|
||||
attr_type = JSON_NUMBER;
|
||||
}
|
||||
} else if (pe) {
|
||||
attr_type = JSON_NULL;
|
||||
}
|
||||
|
||||
if (attr_type != JSON_NULL) {
|
||||
// This knows more about the structure of JSON objects than it ought to
|
||||
|
||||
json_object *ko = (json_object *) malloc(sizeof(json_object));
|
||||
json_object *vo = (json_object *) malloc(sizeof(json_object));
|
||||
if (ko == NULL || vo == NULL) {
|
||||
perror("malloc");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
ko->type = JSON_STRING;
|
||||
vo->type = attr_type;
|
||||
|
||||
ko->parent = vo->parent = properties;
|
||||
ko->array = vo->array = NULL;
|
||||
ko->keys = vo->keys = NULL;
|
||||
ko->values = vo->values = NULL;
|
||||
ko->parser = vo->parser = properties->parser;
|
||||
|
||||
ko->string = strdup(k.c_str());
|
||||
vo->string = strdup(v.c_str());
|
||||
|
||||
if (ko->string == NULL || vo->string == NULL) {
|
||||
perror("strdup");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
ko->length = strlen(ko->string);
|
||||
vo->length = strlen(vo->string);
|
||||
vo->number = atof(vo->string);
|
||||
|
||||
properties->keys[properties->length] = ko;
|
||||
properties->values[properties->length] = vo;
|
||||
properties->length++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct json_join_action : json_feature_action {
|
||||
int add_feature(json_object *geometry, bool, json_object *, json_object *, json_object *, json_object *feature) {
|
||||
if (feature != geometry) { // a real feature, not a bare geometry
|
||||
if (csvfile != NULL) {
|
||||
join_csv(feature);
|
||||
}
|
||||
|
||||
char *s = json_stringify(feature);
|
||||
out(s, 1, json_hash_get(feature, "properties"));
|
||||
free(s);
|
||||
} else {
|
||||
char *s = json_stringify(geometry);
|
||||
out(s, 2, NULL);
|
||||
free(s);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void check_crs(json_object *) {
|
||||
}
|
||||
};
|
||||
|
||||
void process(FILE *fp, const char *fname) {
|
||||
json_pull *jp = json_begin_file(fp);
|
||||
|
||||
json_join_action jja;
|
||||
jja.fname = fname;
|
||||
parse_json(&jja, jp);
|
||||
json_end(jp);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
const char *csv = NULL;
|
||||
|
||||
struct option long_options[] = {
|
||||
{"wrap", no_argument, 0, 'w'},
|
||||
{"extract", required_argument, 0, 'e'},
|
||||
{"csv", required_argument, 0, 'c'},
|
||||
{"empty-csv-columns-are-null", no_argument, &pe, 1},
|
||||
{"prevent", required_argument, 0, 'p'},
|
||||
|
||||
{0, 0, 0, 0},
|
||||
};
|
||||
|
||||
std::string getopt_str;
|
||||
for (size_t lo = 0; long_options[lo].name != NULL; lo++) {
|
||||
if (long_options[lo].val > ' ') {
|
||||
getopt_str.push_back(long_options[lo].val);
|
||||
|
||||
if (long_options[lo].has_arg == required_argument) {
|
||||
getopt_str.push_back(':');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extern int optind;
|
||||
int i;
|
||||
|
||||
while ((i = getopt_long(argc, argv, getopt_str.c_str(), long_options, NULL)) != -1) {
|
||||
switch (i) {
|
||||
case 0:
|
||||
break;
|
||||
|
||||
case 'w':
|
||||
wrap = true;
|
||||
break;
|
||||
|
||||
case 'e':
|
||||
extract = optarg;
|
||||
break;
|
||||
|
||||
case 'c':
|
||||
csv = optarg;
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
if (strcmp(optarg, "e") == 0) {
|
||||
pe = true;
|
||||
} else {
|
||||
fprintf(stderr, "%s: Unknown option for -p%s\n", argv[0], optarg);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
fprintf(stderr, "Unexpected option -%c\n", i);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (extract != NULL && wrap) {
|
||||
fprintf(stderr, "%s: --wrap and --extract not supported together\n", argv[0]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (csv != NULL) {
|
||||
csvfile = fopen(csv, "r");
|
||||
if (csvfile == NULL) {
|
||||
perror(csv);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (optind >= argc) {
|
||||
process(stdin, "standard input");
|
||||
} else {
|
||||
for (i = optind; i < argc; i++) {
|
||||
FILE *f = fopen(argv[i], "r");
|
||||
if (f == NULL) {
|
||||
perror(argv[i]);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
process(f, argv[i]);
|
||||
fclose(f);
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer_state == 1) {
|
||||
printf("%s\n", buffered.c_str());
|
||||
} else if (buffer_state == 2) {
|
||||
printf("]}\n");
|
||||
}
|
||||
|
||||
if (csvfile != NULL) {
|
||||
if (fclose(csvfile) != 0) {
|
||||
perror("close");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
return fail;
|
||||
}
|
50
main.hpp
50
main.hpp
@ -1,18 +1,58 @@
|
||||
#ifndef MAIN_HPP
|
||||
#define MAIN_HPP
|
||||
|
||||
#include <stddef.h>
|
||||
#include <atomic>
|
||||
#include <string>
|
||||
|
||||
struct index {
|
||||
long long start;
|
||||
long long end;
|
||||
unsigned long long index;
|
||||
short segment;
|
||||
long long start = 0;
|
||||
long long end = 0;
|
||||
unsigned long long ix = 0;
|
||||
short segment = 0;
|
||||
unsigned short t : 2;
|
||||
unsigned long long seq : (64 - 18); // pack with segment and t to stay in 32 bytes
|
||||
|
||||
index()
|
||||
: t(0),
|
||||
seq(0) {
|
||||
}
|
||||
};
|
||||
|
||||
void checkdisk(struct reader *r, int nreader);
|
||||
struct clipbbox {
|
||||
double lon1;
|
||||
double lat1;
|
||||
double lon2;
|
||||
double lat2;
|
||||
|
||||
long long minx;
|
||||
long long miny;
|
||||
long long maxx;
|
||||
long long maxy;
|
||||
};
|
||||
|
||||
extern std::vector<clipbbox> clipbboxes;
|
||||
|
||||
void checkdisk(std::vector<struct reader> *r);
|
||||
|
||||
extern int geometry_scale;
|
||||
extern int quiet;
|
||||
extern int quiet_progress;
|
||||
extern double progress_interval;
|
||||
extern std::atomic<double> last_progress;
|
||||
|
||||
extern size_t CPUS;
|
||||
extern size_t TEMP_FILES;
|
||||
|
||||
extern size_t max_tile_size;
|
||||
extern size_t max_tile_features;
|
||||
extern int cluster_distance;
|
||||
extern std::string attribute_for_id;
|
||||
|
||||
int mkstemp_cloexec(char *name);
|
||||
FILE *fopen_oflag(const char *name, const char *mode, int oflag);
|
||||
bool progress_time();
|
||||
|
||||
#define MAX_ZOOM 24
|
||||
|
||||
#endif
|
||||
|
1052
man/tippecanoe.1
1052
man/tippecanoe.1
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,4 @@
|
||||
Parts of the code in the Wagyu Library are derived from the version of the
|
||||
Parts of the code in the Wagyu Library are derived from the version of the
|
||||
Clipper Library by Angus Johnson listed below.
|
||||
|
||||
Author : Angus Johnson
|
||||
@ -6,13 +6,15 @@ Version : 6.4.0
|
||||
Date : 2 July 2015
|
||||
Website : http://www.angusj.com
|
||||
|
||||
Copyright for portions of the derived code in the Wagyu library are held
|
||||
by Angus Johnson, 2010-2015. All other copyright for the Wagyu Library are held by
|
||||
Mapbox, 2016. This code is published in accordance with, and retains the same license
|
||||
as the Clipper Library by Angus Johnson.
|
||||
Copyright for portions of the derived code in the Wagyu library are held
|
||||
by Angus Johnson, 2010-2015. Copyright for the "include/mapbox/geometry/wagyu/almost_equal.hpp"
|
||||
file is held by Google Inc and its license is listed at the top of that file.
|
||||
All other copyright for the Wagyu Library are held by Mapbox, 2016. This code
|
||||
is published in accordance with, and retains the same license as the Clipper
|
||||
Library by Angus Johnson.
|
||||
|
||||
Copyright (c) 2010-2015, Angus Johnson
|
||||
Copyright (c) 2016, Mapbox
|
||||
Copyright (c) 2016-2020, Mapbox
|
||||
|
||||
Permission is hereby granted, free of charge, to any person or organization
|
||||
obtaining a copy of the software and accompanying documentation covered by
|
||||
|
466
mapbox/geometry/snap_rounding.hpp
Normal file
466
mapbox/geometry/snap_rounding.hpp
Normal file
@ -0,0 +1,466 @@
|
||||
#include <mapbox/geometry/geometry.hpp>
|
||||
#include <math.h>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace mapbox {
|
||||
namespace geometry {
|
||||
|
||||
template <typename T>
|
||||
void add_vertical(size_t intermediate, size_t which_end, size_t into, std::vector<std::vector<point<T>>> &segments, bool &again, std::vector<size_t> &nexts) {
|
||||
again = true;
|
||||
std::vector<point<T>> dv;
|
||||
dv.push_back(segments[intermediate][which_end]);
|
||||
dv.push_back(segments[into][1]);
|
||||
segments.push_back(dv);
|
||||
segments[into][1] = segments[intermediate][which_end];
|
||||
nexts.push_back(nexts[into]);
|
||||
nexts[into] = nexts.size() - 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_horizontal(size_t intermediate, size_t which_end, size_t into, std::vector<std::vector<point<T>>> &segments, bool &again, std::vector<size_t> &nexts) {
|
||||
again = true;
|
||||
|
||||
T x = segments[intermediate][which_end].x;
|
||||
T y = segments[intermediate][0].y +
|
||||
(segments[intermediate][which_end].x - segments[intermediate][0].x) *
|
||||
(segments[intermediate][1].y - segments[intermediate][0].y) /
|
||||
(segments[intermediate][1].x - segments[intermediate][0].x);
|
||||
point<T> d(x, y);
|
||||
|
||||
std::vector<point<T>> dv;
|
||||
dv.push_back(d);
|
||||
dv.push_back(segments[into][1]);
|
||||
segments.push_back(dv);
|
||||
segments[into][1] = d;
|
||||
nexts.push_back(nexts[into]);
|
||||
nexts[into] = nexts.size() - 1;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void warn(std::vector<std::vector<point<T>>> &segments, size_t a, size_t b, bool do_warn) {
|
||||
if (do_warn) {
|
||||
fprintf(stderr, "%lld,%lld to %lld,%lld intersects %lld,%lld to %lld,%lld\n",
|
||||
(long long) segments[a][0].x, (long long) segments[a][0].y,
|
||||
(long long) segments[a][1].x, (long long) segments[a][1].y,
|
||||
(long long) segments[b][0].x, (long long) segments[b][0].y,
|
||||
(long long) segments[b][1].x, (long long) segments[b][1].y);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void check_intersection(std::vector<std::vector<point<T>>> &segments, size_t a, size_t b, bool &again, std::vector<size_t> &nexts, bool do_warn, bool endpoint_ok) {
|
||||
T s10_x = segments[a][1].x - segments[a][0].x;
|
||||
T s10_y = segments[a][1].y - segments[a][0].y;
|
||||
T s32_x = segments[b][1].x - segments[b][0].x;
|
||||
T s32_y = segments[b][1].y - segments[b][0].y;
|
||||
|
||||
// http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect
|
||||
T denom = s10_x * s32_y - s32_x * s10_y;
|
||||
|
||||
if (denom == 0) {
|
||||
// They are parallel or collinear. Find out if they are collinear.
|
||||
// http://www.cpsc.ucalgary.ca/~marina/papers/Segment_intersection.ps
|
||||
|
||||
T ccw =
|
||||
segments[a][0].x * segments[a][1].y +
|
||||
segments[a][1].x * segments[b][0].y +
|
||||
segments[b][0].x * segments[a][0].y -
|
||||
segments[a][0].x * segments[b][0].y -
|
||||
segments[a][1].x * segments[a][0].y -
|
||||
segments[b][0].x * segments[a][1].y;
|
||||
|
||||
if (ccw == 0) {
|
||||
if (segments[a][0].x == segments[a][1].x) {
|
||||
// Vertical
|
||||
|
||||
T amin, amax, bmin, bmax;
|
||||
if (segments[a][0].y < segments[a][1].y) {
|
||||
amin = segments[a][0].y;
|
||||
amax = segments[a][1].y;
|
||||
} else {
|
||||
amin = segments[a][1].y;
|
||||
amax = segments[a][0].y;
|
||||
}
|
||||
if (segments[b][0].y < segments[b][1].y) {
|
||||
bmin = segments[b][0].y;
|
||||
bmax = segments[b][1].y;
|
||||
} else {
|
||||
bmin = segments[b][1].y;
|
||||
bmax = segments[b][0].y;
|
||||
}
|
||||
|
||||
// All of these transformations preserve verticality so we can check multiple cases
|
||||
if (segments[b][0].y > amin && segments[b][0].y < amax) {
|
||||
// B0 is in A
|
||||
warn(segments, a, b, do_warn);
|
||||
add_vertical(b, 0, a, segments, again, nexts);
|
||||
}
|
||||
if (segments[b][1].y > amin && segments[b][1].y < amax) {
|
||||
// B1 is in A
|
||||
warn(segments, a, b, do_warn);
|
||||
add_vertical(b, 1, a, segments, again, nexts);
|
||||
}
|
||||
if (segments[a][0].y > bmin && segments[a][0].y < bmax) {
|
||||
// A0 is in B
|
||||
warn(segments, a, b, do_warn);
|
||||
add_vertical(a, 0, b, segments, again, nexts);
|
||||
}
|
||||
if (segments[a][1].y > bmin && segments[a][1].y < bmax) {
|
||||
// A1 is in B
|
||||
warn(segments, a, b, do_warn);
|
||||
add_vertical(a, 1, b, segments, again, nexts);
|
||||
}
|
||||
} else {
|
||||
// Horizontal or diagonal
|
||||
|
||||
T amin, amax, bmin, bmax;
|
||||
if (segments[a][0].x < segments[a][1].x) {
|
||||
amin = segments[a][0].x;
|
||||
amax = segments[a][1].x;
|
||||
} else {
|
||||
amin = segments[a][1].x;
|
||||
amax = segments[a][0].x;
|
||||
}
|
||||
if (segments[b][0].x < segments[b][1].x) {
|
||||
bmin = segments[b][0].x;
|
||||
bmax = segments[b][1].x;
|
||||
} else {
|
||||
bmin = segments[b][1].x;
|
||||
bmax = segments[b][0].x;
|
||||
}
|
||||
|
||||
// Don't check multiples, because rounding may corrupt collinearity
|
||||
if (segments[b][0].x > amin && segments[b][0].x < amax) {
|
||||
// B0 is in A
|
||||
add_horizontal(b, 0, a, segments, again, nexts);
|
||||
warn(segments, a, b, do_warn);
|
||||
} else if (segments[b][1].x > amin && segments[b][1].x < amax) {
|
||||
// B1 is in A
|
||||
add_horizontal(b, 1, a, segments, again, nexts);
|
||||
warn(segments, a, b, do_warn);
|
||||
} else if (segments[a][0].x > bmin && segments[a][0].x < bmax) {
|
||||
// A0 is in B
|
||||
warn(segments, a, b, do_warn);
|
||||
add_horizontal(a, 0, b, segments, again, nexts);
|
||||
} else if (segments[a][1].x > bmin && segments[a][1].x < bmax) {
|
||||
// A1 is in B
|
||||
warn(segments, a, b, do_warn);
|
||||
add_horizontal(a, 1, b, segments, again, nexts);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Neither parallel nor collinear, so may intersect at a single point
|
||||
|
||||
T s02_x = segments[a][0].x - segments[b][0].x;
|
||||
T s02_y = segments[a][0].y - segments[b][0].y;
|
||||
|
||||
double s = (s10_x * s02_y - s10_y * s02_x) / (long double) denom;
|
||||
double t = (s32_x * s02_y - s32_y * s02_x) / (long double) denom;
|
||||
|
||||
if (t >= 0 && t <= 1 && s >= 0 && s <= 1) {
|
||||
T x = (T) round(segments[a][0].x + t * s10_x);
|
||||
T y = (T) round(segments[a][0].y + t * s10_y);
|
||||
|
||||
if ((t > 0 && t < 1 && s > 0 && s < 1) || !endpoint_ok) {
|
||||
if (t >= 0 && t <= 1) {
|
||||
if ((x != segments[a][0].x || y != segments[a][0].y) && (x != segments[a][1].x || y != segments[a][1].y)) {
|
||||
warn(segments, a, b, do_warn);
|
||||
// splitting a
|
||||
std::vector<point<T>> dv;
|
||||
dv.push_back(point<T>(x, y));
|
||||
dv.push_back(segments[a][1]);
|
||||
segments.push_back(dv);
|
||||
segments[a][1] = point<T>(x, y);
|
||||
nexts.push_back(nexts[a]);
|
||||
nexts[a] = nexts.size() - 1;
|
||||
again = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (s >= 0 && s <= 1) {
|
||||
if ((x != segments[b][0].x || y != segments[b][0].y) && (x != segments[b][1].x || y != segments[b][1].y)) {
|
||||
// splitting b
|
||||
warn(segments, a, b, do_warn);
|
||||
std::vector<point<T>> dv;
|
||||
dv.push_back(point<T>(x, y));
|
||||
dv.push_back(segments[b][1]);
|
||||
segments.push_back(dv);
|
||||
segments[b][1] = point<T>(x, y);
|
||||
nexts.push_back(nexts[b]);
|
||||
nexts[b] = nexts.size() - 1;
|
||||
again = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void partition(std::vector<std::vector<point<T>>> &segs, std::vector<size_t> &subset, int direction, std::set<std::pair<size_t, size_t>> &possible) {
|
||||
std::vector<T> points;
|
||||
|
||||
// List of X or Y midpoints of edges, so we can find the median
|
||||
|
||||
if (direction == 0) {
|
||||
for (size_t i = 0; i < subset.size(); i++) {
|
||||
points.push_back((segs[subset[i]][0].x + segs[subset[i]][1].x) / 2);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < subset.size(); i++) {
|
||||
points.push_back((segs[subset[i]][0].y + segs[subset[i]][1].y) / 2);
|
||||
}
|
||||
}
|
||||
|
||||
if (points.size() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
size_t mid = points.size() / 2;
|
||||
std::nth_element(points.begin(), points.begin() + mid, points.end());
|
||||
T median = points[mid];
|
||||
|
||||
// Partition into sets that are above or below, or to the left or to the right of, the median.
|
||||
// Segments that cross the median appear in both.
|
||||
|
||||
std::vector<size_t> one;
|
||||
std::vector<size_t> two;
|
||||
|
||||
if (direction == 0) {
|
||||
for (size_t i = 0; i < subset.size(); i++) {
|
||||
if (segs[subset[i]][0].x <= median || segs[subset[i]][1].x <= median) {
|
||||
one.push_back(subset[i]);
|
||||
}
|
||||
if (segs[subset[i]][0].x >= median || segs[subset[i]][1].x >= median) {
|
||||
two.push_back(subset[i]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < subset.size(); i++) {
|
||||
if (segs[subset[i]][0].y <= median || segs[subset[i]][1].y <= median) {
|
||||
one.push_back(subset[i]);
|
||||
}
|
||||
if (segs[subset[i]][0].y >= median || segs[subset[i]][1].y >= median) {
|
||||
two.push_back(subset[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (one.size() >= subset.size() || two.size() >= subset.size()) {
|
||||
for (size_t i = 0; i < subset.size(); i++) {
|
||||
for (size_t j = i + 1; j < subset.size(); j++) {
|
||||
possible.insert(std::pair<size_t, size_t>(subset[i], subset[j]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// By experiment, stopping at 10 is a little faster than either 5 or 20
|
||||
|
||||
if (one.size() < 10) {
|
||||
for (size_t i = 0; i < one.size(); i++) {
|
||||
for (size_t j = i + 1; j < one.size(); j++) {
|
||||
possible.insert(std::pair<size_t, size_t>(one[i], one[j]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
partition(segs, one, !direction, possible);
|
||||
}
|
||||
|
||||
if (two.size() < 10) {
|
||||
for (size_t i = 0; i < two.size(); i++) {
|
||||
for (size_t j = i + 1; j < two.size(); j++) {
|
||||
possible.insert(std::pair<size_t, size_t>(two[i], two[j]));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
partition(segs, two, !direction, possible);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::vector<std::vector<point<T>>> intersect_segments(std::vector<std::vector<point<T>>> segments, std::vector<size_t> &nexts, bool do_warn, bool endpoint_ok) {
|
||||
bool again = true;
|
||||
|
||||
while (again) {
|
||||
again = false;
|
||||
|
||||
std::set<std::pair<size_t, size_t>> possible;
|
||||
|
||||
std::vector<size_t> subset;
|
||||
for (size_t i = 0; i < segments.size(); i++) {
|
||||
subset.push_back(i);
|
||||
}
|
||||
|
||||
partition(segments, subset, 0, possible);
|
||||
|
||||
for (auto it = possible.begin(); it != possible.end(); ++it) {
|
||||
check_intersection(segments, it->first, it->second, again, nexts, do_warn, endpoint_ok);
|
||||
}
|
||||
}
|
||||
|
||||
return segments;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
linear_ring<T> remove_collinear(linear_ring<T> ring) {
|
||||
linear_ring<T> out;
|
||||
|
||||
size_t len = ring.size() - 1; // Exclude duplicated last point
|
||||
for (size_t j = 0; j < len; j++) {
|
||||
long long ccw =
|
||||
ring[(j + len - 1) % len].x * ring[(j + len - 0) % len].y +
|
||||
ring[(j + len - 0) % len].x * ring[(j + len + 1) % len].y +
|
||||
ring[(j + len + 1) % len].x * ring[(j + len - 1) % len].y -
|
||||
ring[(j + len - 1) % len].x * ring[(j + len + 1) % len].y -
|
||||
ring[(j + len - 0) % len].x * ring[(j + len - 1) % len].y -
|
||||
ring[(j + len + 1) % len].x * ring[(j + len - 0) % len].y;
|
||||
|
||||
if (ccw != 0) {
|
||||
out.push_back(ring[j]);
|
||||
}
|
||||
|
||||
if (ring.size() > 0 && ring[0] != ring[ring.size() - 1]) {
|
||||
ring.push_back(ring[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
multi_polygon<T> snap_round(multi_polygon<T> geom, bool do_warn, bool endpoint_ok) {
|
||||
std::vector<std::vector<point<T>>> segments;
|
||||
std::vector<size_t> nexts;
|
||||
std::vector<std::vector<size_t>> ring_starts;
|
||||
|
||||
// Crunch out any 0-length segments
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
for (size_t j = 0; j < geom[i].size(); j++) {
|
||||
for (ssize_t k = geom[i][j].size() - 1; k > 0; k--) {
|
||||
if (geom[i][j][k] == geom[i][j][k - 1]) {
|
||||
geom[i][j].erase(geom[i][j].begin() + k);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
ring_starts.push_back(std::vector<size_t>());
|
||||
|
||||
for (size_t j = 0; j < geom[i].size(); j++) {
|
||||
size_t s = geom[i][j].size();
|
||||
|
||||
if (s > 1) {
|
||||
ring_starts[i].push_back(segments.size());
|
||||
size_t first = nexts.size();
|
||||
|
||||
for (size_t k = 0; k + 1 < s; k++) {
|
||||
std::vector<point<T>> dv;
|
||||
dv.push_back(geom[i][j][k]);
|
||||
dv.push_back(geom[i][j][k + 1]);
|
||||
|
||||
segments.push_back(dv);
|
||||
nexts.push_back(nexts.size() + 1);
|
||||
}
|
||||
|
||||
// Fabricate a point if ring was not closed
|
||||
if (geom[i][j][0] != geom[i][j][s - 1]) {
|
||||
std::vector<point<T>> dv;
|
||||
dv.push_back(geom[i][j][s - 1]);
|
||||
dv.push_back(geom[i][j][0]);
|
||||
|
||||
segments.push_back(dv);
|
||||
nexts.push_back(nexts.size() + 1);
|
||||
}
|
||||
|
||||
// Last point of ring points back to first
|
||||
nexts[nexts.size() - 1] = first;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
segments = intersect_segments(segments, nexts, do_warn, endpoint_ok);
|
||||
|
||||
multi_polygon<T> mp;
|
||||
for (size_t i = 0; i < ring_starts.size(); i++) {
|
||||
mp.push_back(polygon<T>());
|
||||
|
||||
for (size_t j = 0; j < ring_starts[i].size(); j++) {
|
||||
mp[i].push_back(linear_ring<T>());
|
||||
|
||||
size_t k = ring_starts[i][j];
|
||||
do {
|
||||
mp[i][j].push_back(segments[k][0]);
|
||||
k = nexts[k];
|
||||
} while (k != ring_starts[i][j]);
|
||||
|
||||
mp[i][j].push_back(segments[ring_starts[i][j]][0]);
|
||||
}
|
||||
}
|
||||
|
||||
return mp;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
multi_line_string<T> snap_round(multi_line_string<T> geom, bool do_warn, bool endpoint_ok) {
|
||||
std::vector<std::vector<point<T>>> segments;
|
||||
std::vector<size_t> nexts;
|
||||
std::vector<size_t> ring_starts;
|
||||
|
||||
// Crunch out any 0-length segments
|
||||
for (size_t j = 0; j < geom.size(); j++) {
|
||||
for (ssize_t k = geom[j].size() - 1; k > 0; k--) {
|
||||
if (geom[j][k] == geom[j][k - 1]) {
|
||||
geom[j].erase(geom[j].begin() + k);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t j = 0; j < geom.size(); j++) {
|
||||
size_t s = geom[j].size();
|
||||
|
||||
if (s > 1) {
|
||||
ring_starts.push_back(segments.size());
|
||||
size_t first = nexts.size();
|
||||
|
||||
for (size_t k = 0; k + 1 < s; k++) {
|
||||
std::vector<point<T>> dv;
|
||||
dv.push_back(geom[j][k]);
|
||||
dv.push_back(geom[j][k + 1]);
|
||||
|
||||
segments.push_back(dv);
|
||||
nexts.push_back(nexts.size() + 1);
|
||||
}
|
||||
|
||||
// Last point of ring points back to first
|
||||
nexts[nexts.size() - 1] = first;
|
||||
}
|
||||
}
|
||||
|
||||
segments = intersect_segments(segments, nexts, do_warn, endpoint_ok);
|
||||
|
||||
multi_line_string<T> mp;
|
||||
for (size_t j = 0; j < ring_starts.size(); j++) {
|
||||
mp.push_back(line_string<T>());
|
||||
|
||||
size_t k = ring_starts[j];
|
||||
size_t last = k;
|
||||
do {
|
||||
mp[j].push_back(segments[k][0]);
|
||||
last = k;
|
||||
k = nexts[k];
|
||||
} while (k != ring_starts[j]);
|
||||
|
||||
mp[j].push_back(segments[last][1]);
|
||||
}
|
||||
|
||||
return mp;
|
||||
}
|
||||
}
|
||||
}
|
@ -8,7 +8,6 @@
|
||||
#include <mapbox/geometry/wagyu/bound.hpp>
|
||||
#include <mapbox/geometry/wagyu/config.hpp>
|
||||
#include <mapbox/geometry/wagyu/edge.hpp>
|
||||
#include <mapbox/geometry/wagyu/exceptions.hpp>
|
||||
#include <mapbox/geometry/wagyu/local_minimum.hpp>
|
||||
#include <mapbox/geometry/wagyu/local_minimum_util.hpp>
|
||||
#include <mapbox/geometry/wagyu/ring.hpp>
|
||||
@ -20,7 +19,7 @@ namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
template <typename T>
|
||||
using active_bound_list = std::list<bound_ptr<T>>;
|
||||
using active_bound_list = std::vector<bound_ptr<T>>;
|
||||
|
||||
template <typename T>
|
||||
using active_bound_list_itr = typename active_bound_list<T>::iterator;
|
||||
@ -62,9 +61,7 @@ std::string output_edges(active_bound_list<T> const& bnds) {
|
||||
#endif
|
||||
|
||||
template <typename T>
|
||||
bool is_even_odd_fill_type(bound<T> const& bound,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
bool is_even_odd_fill_type(bound<T> const& bound, fill_type subject_fill_type, fill_type clip_fill_type) {
|
||||
if (bound.poly_type == polygon_type_subject) {
|
||||
return subject_fill_type == fill_type_even_odd;
|
||||
} else {
|
||||
@ -73,9 +70,7 @@ bool is_even_odd_fill_type(bound<T> const& bound,
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool is_even_odd_alt_fill_type(bound<T> const& bound,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
bool is_even_odd_alt_fill_type(bound<T> const& bound, fill_type subject_fill_type, fill_type clip_fill_type) {
|
||||
if (bound.poly_type == polygon_type_subject) {
|
||||
return clip_fill_type == fill_type_even_odd;
|
||||
} else {
|
||||
@ -84,163 +79,114 @@ bool is_even_odd_alt_fill_type(bound<T> const& bound,
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool bound2_inserts_before_bound1(bound<T> const& bound1, bound<T> const& bound2) {
|
||||
if (values_are_equal(bound2.current_x, bound1.current_x)) {
|
||||
if (bound2.current_edge->top.y > bound1.current_edge->top.y) {
|
||||
return bound2.current_edge->top.x <
|
||||
get_current_x(*(bound1.current_edge), bound2.current_edge->top.y);
|
||||
struct bound_insert_location {
|
||||
|
||||
bound<T> const& bound2;
|
||||
|
||||
bound_insert_location(bound<T> const& b) : bound2(b) {
|
||||
}
|
||||
|
||||
bool operator()(bound_ptr<T> const& b) {
|
||||
auto const& bound1 = *b;
|
||||
if (values_are_equal(bound2.current_x, bound1.current_x)) {
|
||||
if (bound2.current_edge->top.y > bound1.current_edge->top.y) {
|
||||
return less_than(static_cast<double>(bound2.current_edge->top.x),
|
||||
get_current_x(*(bound1.current_edge), bound2.current_edge->top.y));
|
||||
} else {
|
||||
return greater_than(static_cast<double>(bound1.current_edge->top.x),
|
||||
get_current_x(*(bound2.current_edge), bound1.current_edge->top.y));
|
||||
}
|
||||
} else {
|
||||
return bound1.current_edge->top.x >
|
||||
get_current_x(*(bound2.current_edge), bound1.current_edge->top.y);
|
||||
return bound2.current_x < bound1.current_x;
|
||||
}
|
||||
} else {
|
||||
return bound2.current_x < bound1.current_x;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
active_bound_list_itr<T> insert_bound_into_ABL(bound<T>& left, bound<T>& right, active_bound_list<T>& active_bounds) {
|
||||
|
||||
auto itr = std::find_if(active_bounds.begin(), active_bounds.end(), bound_insert_location<T>(left));
|
||||
#ifdef GCC_MISSING_VECTOR_RANGE_INSERT
|
||||
itr = active_bounds.insert(itr, &right);
|
||||
return active_bounds.insert(itr, &left);
|
||||
#else
|
||||
return active_bounds.insert(itr, { &left, &right });
|
||||
#endif
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
active_bound_list_itr<T> insert_bound_into_ABL(bound<T>& bnd, active_bound_list<T>& active_bounds) {
|
||||
auto itr = active_bounds.begin();
|
||||
while (itr != active_bounds.end() && !bound2_inserts_before_bound1(*(*itr), bnd)) {
|
||||
++itr;
|
||||
}
|
||||
return active_bounds.insert(itr, &bnd);
|
||||
inline bool is_maxima(bound<T> const& bnd, T y) {
|
||||
return bnd.next_edge == bnd.edges.end() && bnd.current_edge->top.y == y;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
active_bound_list_itr<T> insert_bound_into_ABL(bound<T>& bnd,
|
||||
active_bound_list_itr<T> itr,
|
||||
active_bound_list<T>& active_bounds) {
|
||||
while (itr != active_bounds.end() && !bound2_inserts_before_bound1(*(*itr), bnd)) {
|
||||
++itr;
|
||||
}
|
||||
return active_bounds.insert(itr, &bnd);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool is_maxima(bound<T>& bnd, T y) {
|
||||
return bnd.next_edge == bnd.edges.end() &&
|
||||
bnd.current_edge->top.y == y;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool is_maxima(active_bound_list_itr<T>& bnd, T y) {
|
||||
inline bool is_maxima(active_bound_list_itr<T> const& bnd, T y) {
|
||||
return is_maxima(*(*bnd), y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool is_intermediate(bound<T>& bnd, T y) {
|
||||
return bnd.next_edge != bnd.edges.end() &&
|
||||
bnd.current_edge->top.y == y;
|
||||
inline bool is_intermediate(bound<T> const& bnd, T y) {
|
||||
return bnd.next_edge != bnd.edges.end() && bnd.current_edge->top.y == y;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool is_intermediate(active_bound_list_itr<T>& bnd, T y) {
|
||||
inline bool is_intermediate(active_bound_list_itr<T> const& bnd, T y) {
|
||||
return is_intermediate(*(*bnd), y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool current_edge_is_horizontal(active_bound_list_itr<T>& bnd) {
|
||||
inline bool current_edge_is_horizontal(active_bound_list_itr<T> const& bnd) {
|
||||
return is_horizontal(*((*bnd)->current_edge));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool next_edge_is_horizontal(active_bound_list_itr<T>& bnd) {
|
||||
inline bool next_edge_is_horizontal(active_bound_list_itr<T> const& bnd) {
|
||||
return is_horizontal(*((*bnd)->next_edge));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void swap_positions_in_ABL(active_bound_list_itr<T>& bnd1,
|
||||
active_bound_list_itr<T>& bnd2,
|
||||
active_bound_list<T>& active_bounds) {
|
||||
if (std::next(bnd2) == bnd1) {
|
||||
active_bounds.splice(bnd2, active_bounds, bnd1);
|
||||
} else {
|
||||
active_bounds.splice(bnd1, active_bounds, bnd2);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void next_edge_in_bound(active_bound_list_itr<T>& bnd, scanbeam_list<T>& scanbeam) {
|
||||
++((*bnd)->current_edge);
|
||||
if ((*bnd)->current_edge != (*bnd)->edges.end()) {
|
||||
++((*bnd)->next_edge);
|
||||
(*bnd)->current_x = static_cast<double>((*bnd)->current_edge->bot.x);
|
||||
if (!current_edge_is_horizontal<T>(bnd)) {
|
||||
scanbeam.push((*bnd)->current_edge->top.y);
|
||||
void next_edge_in_bound(bound<T>& bnd, scanbeam_list<T>& scanbeam) {
|
||||
auto& current_edge = bnd.current_edge;
|
||||
++current_edge;
|
||||
if (current_edge != bnd.edges.end()) {
|
||||
++(bnd.next_edge);
|
||||
bnd.current_x = static_cast<double>(current_edge->bot.x);
|
||||
if (!is_horizontal<T>(*current_edge)) {
|
||||
insert_sorted_scanbeam(scanbeam, current_edge->top.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
active_bound_list_itr<T> get_maxima_pair(active_bound_list_itr<T> bnd,
|
||||
active_bound_list<T>& active_bounds) {
|
||||
auto bnd_itr = active_bounds.begin();
|
||||
while (bnd_itr != active_bounds.end()) {
|
||||
if (*bnd_itr == (*bnd)->maximum_bound) {
|
||||
break;
|
||||
}
|
||||
++bnd_itr;
|
||||
}
|
||||
return bnd_itr;
|
||||
active_bound_list_itr<T> get_maxima_pair(active_bound_list_itr<T> bnd, active_bound_list<T>& active_bounds) {
|
||||
bound_ptr<T> maximum = (*bnd)->maximum_bound;
|
||||
return std::find(active_bounds.begin(), active_bounds.end(), maximum);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void set_winding_count(active_bound_list_itr<T>& bnd_itr,
|
||||
void set_winding_count(active_bound_list_itr<T> bnd_itr,
|
||||
active_bound_list<T>& active_bounds,
|
||||
clip_type cliptype,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
|
||||
auto rev_bnd_itr = active_bound_list_rev_itr<T>(bnd_itr);
|
||||
if (rev_bnd_itr == active_bounds.rend()) {
|
||||
if ((*bnd_itr)->winding_delta == 0) {
|
||||
fill_type pft = ((*bnd_itr)->poly_type == polygon_type_subject) ? subject_fill_type
|
||||
: clip_fill_type;
|
||||
(*bnd_itr)->winding_count = (pft == fill_type_negative ? -1 : 1);
|
||||
} else {
|
||||
(*bnd_itr)->winding_count = (*bnd_itr)->winding_delta;
|
||||
}
|
||||
(*bnd_itr)->winding_count = (*bnd_itr)->winding_delta;
|
||||
(*bnd_itr)->winding_count2 = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// find the edge of the same polytype that immediately preceeds 'edge' in
|
||||
// AEL
|
||||
while (rev_bnd_itr != active_bounds.rend() &&
|
||||
((*rev_bnd_itr)->poly_type != (*bnd_itr)->poly_type ||
|
||||
(*rev_bnd_itr)->winding_delta == 0)) {
|
||||
while (rev_bnd_itr != active_bounds.rend() && (*rev_bnd_itr)->poly_type != (*bnd_itr)->poly_type) {
|
||||
++rev_bnd_itr;
|
||||
}
|
||||
if (rev_bnd_itr == active_bounds.rend()) {
|
||||
if ((*bnd_itr)->winding_delta == 0) {
|
||||
fill_type pft = ((*bnd_itr)->poly_type == polygon_type_subject) ? subject_fill_type
|
||||
: clip_fill_type;
|
||||
(*bnd_itr)->winding_count = (pft == fill_type_negative ? -1 : 1);
|
||||
} else {
|
||||
(*bnd_itr)->winding_count = (*bnd_itr)->winding_delta;
|
||||
}
|
||||
(*bnd_itr)->winding_count = (*bnd_itr)->winding_delta;
|
||||
(*bnd_itr)->winding_count2 = 0;
|
||||
} else if ((*bnd_itr)->winding_delta == 0 && cliptype != clip_type_union) {
|
||||
(*bnd_itr)->winding_count = 1;
|
||||
(*bnd_itr)->winding_count2 = (*rev_bnd_itr)->winding_count2;
|
||||
} else if (is_even_odd_fill_type(*(*bnd_itr), subject_fill_type, clip_fill_type)) {
|
||||
// EvenOdd filling ...
|
||||
if ((*bnd_itr)->winding_delta == 0) {
|
||||
// are we inside a subj polygon ...
|
||||
bool inside = true;
|
||||
auto rev2 = std::next(rev_bnd_itr);
|
||||
while (rev2 != active_bounds.rend()) {
|
||||
if ((*rev2)->poly_type == (*rev_bnd_itr)->poly_type &&
|
||||
(*rev2)->winding_delta != 0) {
|
||||
inside = !inside;
|
||||
}
|
||||
++rev2;
|
||||
}
|
||||
(*bnd_itr)->winding_count = (inside ? 0 : 1);
|
||||
} else {
|
||||
(*bnd_itr)->winding_count = (*bnd_itr)->winding_delta;
|
||||
}
|
||||
(*bnd_itr)->winding_count = (*bnd_itr)->winding_delta;
|
||||
(*bnd_itr)->winding_count2 = (*rev_bnd_itr)->winding_count2;
|
||||
} else {
|
||||
// nonZero, Positive or Negative filling ...
|
||||
@ -254,28 +200,21 @@ void set_winding_count(active_bound_list_itr<T>& bnd_itr,
|
||||
(*bnd_itr)->winding_count = (*rev_bnd_itr)->winding_count;
|
||||
} else {
|
||||
// otherwise continue to 'decrease' WC ...
|
||||
(*bnd_itr)->winding_count =
|
||||
(*rev_bnd_itr)->winding_count + (*bnd_itr)->winding_delta;
|
||||
(*bnd_itr)->winding_count = (*rev_bnd_itr)->winding_count + (*bnd_itr)->winding_delta;
|
||||
}
|
||||
} else {
|
||||
// now outside all polys of same polytype so set own WC ...
|
||||
(*bnd_itr)->winding_count =
|
||||
((*bnd_itr)->winding_delta == 0 ? 1 : (*bnd_itr)->winding_delta);
|
||||
(*bnd_itr)->winding_count = (*bnd_itr)->winding_delta;
|
||||
}
|
||||
} else {
|
||||
// prev edge is 'increasing' WindCount (WC) away from zero
|
||||
// so we're inside the previous polygon ...
|
||||
if ((*bnd_itr)->winding_delta == 0) {
|
||||
(*bnd_itr)->winding_count =
|
||||
((*rev_bnd_itr)->winding_count < 0 ? (*rev_bnd_itr)->winding_count - 1
|
||||
: (*rev_bnd_itr)->winding_count + 1);
|
||||
} else if ((*rev_bnd_itr)->winding_delta * (*bnd_itr)->winding_delta < 0) {
|
||||
if ((*rev_bnd_itr)->winding_delta * (*bnd_itr)->winding_delta < 0) {
|
||||
// if wind direction is reversing prev then use same WC
|
||||
(*bnd_itr)->winding_count = (*rev_bnd_itr)->winding_count;
|
||||
} else {
|
||||
// otherwise add to WC ...
|
||||
(*bnd_itr)->winding_count =
|
||||
(*rev_bnd_itr)->winding_count + (*bnd_itr)->winding_delta;
|
||||
(*bnd_itr)->winding_count = (*rev_bnd_itr)->winding_count + (*bnd_itr)->winding_delta;
|
||||
}
|
||||
}
|
||||
(*bnd_itr)->winding_count2 = (*rev_bnd_itr)->winding_count2;
|
||||
@ -286,9 +225,7 @@ void set_winding_count(active_bound_list_itr<T>& bnd_itr,
|
||||
if (is_even_odd_alt_fill_type(*(*bnd_itr), subject_fill_type, clip_fill_type)) {
|
||||
// EvenOdd filling ...
|
||||
while (bnd_itr_forward != bnd_itr) {
|
||||
if ((*bnd_itr_forward)->winding_delta != 0) {
|
||||
(*bnd_itr)->winding_count2 = ((*bnd_itr)->winding_count2 == 0 ? 1 : 0);
|
||||
}
|
||||
(*bnd_itr)->winding_count2 = ((*bnd_itr)->winding_count2 == 0 ? 1 : 0);
|
||||
++bnd_itr_forward;
|
||||
}
|
||||
} else {
|
||||
@ -301,10 +238,7 @@ void set_winding_count(active_bound_list_itr<T>& bnd_itr,
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool is_contributing(bound<T> const& bnd,
|
||||
clip_type cliptype,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
bool is_contributing(bound<T> const& bnd, clip_type cliptype, fill_type subject_fill_type, fill_type clip_fill_type) {
|
||||
fill_type pft = subject_fill_type;
|
||||
fill_type pft2 = clip_fill_type;
|
||||
if (bnd.poly_type != polygon_type_subject) {
|
||||
@ -314,11 +248,6 @@ bool is_contributing(bound<T> const& bnd,
|
||||
|
||||
switch (pft) {
|
||||
case fill_type_even_odd:
|
||||
// return false if a subj line has been flagged as inside a subj
|
||||
// polygon
|
||||
if (bnd.winding_delta == 0 && bnd.winding_count != 1) {
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case fill_type_non_zero:
|
||||
if (std::abs(static_cast<int>(bnd.winding_count)) != 1) {
|
||||
@ -388,45 +317,13 @@ bool is_contributing(bound<T> const& bnd,
|
||||
}
|
||||
break;
|
||||
case clip_type_x_or:
|
||||
if (bnd.winding_delta == 0) {
|
||||
// XOr always contributing unless open
|
||||
switch (pft2) {
|
||||
case fill_type_even_odd:
|
||||
case fill_type_non_zero:
|
||||
return (bnd.winding_count2 == 0);
|
||||
case fill_type_positive:
|
||||
return (bnd.winding_count2 <= 0);
|
||||
case fill_type_negative:
|
||||
default:
|
||||
return (bnd.winding_count2 >= 0);
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
break;
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void insert_lm_only_one_bound(bound<T>& bnd,
|
||||
active_bound_list<T>& active_bounds,
|
||||
ring_manager<T>& rings,
|
||||
scanbeam_list<T>& scanbeam,
|
||||
clip_type cliptype,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
auto abl_itr = insert_bound_into_ABL(bnd, active_bounds);
|
||||
set_winding_count(abl_itr, active_bounds, cliptype, subject_fill_type, clip_fill_type);
|
||||
if (is_contributing(bnd, cliptype, subject_fill_type, clip_fill_type)) {
|
||||
add_first_point(abl_itr, active_bounds, (*abl_itr)->current_edge->bot, rings);
|
||||
}
|
||||
if (!current_edge_is_horizontal<T>(abl_itr)) {
|
||||
scanbeam.push((*abl_itr)->current_edge->top.y);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void insert_lm_left_and_right_bound(bound<T>& left_bound,
|
||||
bound<T>& right_bound,
|
||||
@ -438,31 +335,20 @@ void insert_lm_left_and_right_bound(bound<T>& left_bound,
|
||||
fill_type clip_fill_type) {
|
||||
|
||||
// Both left and right bound
|
||||
auto lb_abl_itr = insert_bound_into_ABL(left_bound, active_bounds);
|
||||
auto rb_abl_itr = insert_bound_into_ABL(right_bound, lb_abl_itr, active_bounds);
|
||||
set_winding_count(lb_abl_itr, active_bounds, cliptype, subject_fill_type, clip_fill_type);
|
||||
auto lb_abl_itr = insert_bound_into_ABL(left_bound, right_bound, active_bounds);
|
||||
auto rb_abl_itr = std::next(lb_abl_itr);
|
||||
set_winding_count(lb_abl_itr, active_bounds, subject_fill_type, clip_fill_type);
|
||||
(*rb_abl_itr)->winding_count = (*lb_abl_itr)->winding_count;
|
||||
(*rb_abl_itr)->winding_count2 = (*lb_abl_itr)->winding_count2;
|
||||
if (is_contributing(left_bound, cliptype, subject_fill_type, clip_fill_type)) {
|
||||
add_local_minimum_point(lb_abl_itr, rb_abl_itr, active_bounds,
|
||||
(*lb_abl_itr)->current_edge->bot, rings);
|
||||
add_local_minimum_point(*(*lb_abl_itr), *(*rb_abl_itr), active_bounds, (*lb_abl_itr)->current_edge->bot, rings);
|
||||
}
|
||||
|
||||
// Add top of edges to scanbeam
|
||||
scanbeam.push((*lb_abl_itr)->current_edge->top.y);
|
||||
insert_sorted_scanbeam(scanbeam, (*lb_abl_itr)->current_edge->top.y);
|
||||
|
||||
if (!current_edge_is_horizontal<T>(rb_abl_itr)) {
|
||||
scanbeam.push((*rb_abl_itr)->current_edge->top.y);
|
||||
}
|
||||
auto abl_itr = std::next(lb_abl_itr);
|
||||
while (abl_itr != rb_abl_itr && abl_itr != active_bounds.end()) {
|
||||
// We call intersect_bounds here, but we do not swap positions in the ABL
|
||||
// this is the logic that was copied from angus, but it might be correct
|
||||
// to swap the positions in the ABL following this or at least move
|
||||
// lb and rb to be next to each other in the ABL.
|
||||
intersect_bounds(rb_abl_itr, abl_itr, (*lb_abl_itr)->current_edge->bot, cliptype,
|
||||
subject_fill_type, clip_fill_type, rings, active_bounds);
|
||||
++abl_itr;
|
||||
insert_sorted_scanbeam(scanbeam, (*rb_abl_itr)->current_edge->top.y);
|
||||
}
|
||||
}
|
||||
|
||||
@ -480,16 +366,8 @@ void insert_local_minima_into_ABL(T const bot_y,
|
||||
initialize_lm<T>(current_lm);
|
||||
auto& left_bound = (*current_lm)->left_bound;
|
||||
auto& right_bound = (*current_lm)->right_bound;
|
||||
if (left_bound.edges.empty() && !right_bound.edges.empty()) {
|
||||
insert_lm_only_one_bound(right_bound, active_bounds, rings, scanbeam, cliptype,
|
||||
subject_fill_type, clip_fill_type);
|
||||
} else if (right_bound.edges.empty() && !left_bound.edges.empty()) {
|
||||
insert_lm_only_one_bound(left_bound, active_bounds, rings, scanbeam, cliptype,
|
||||
subject_fill_type, clip_fill_type);
|
||||
} else {
|
||||
insert_lm_left_and_right_bound(left_bound, right_bound, active_bounds, rings, scanbeam,
|
||||
cliptype, subject_fill_type, clip_fill_type);
|
||||
}
|
||||
insert_lm_left_and_right_bound(left_bound, right_bound, active_bounds, rings, scanbeam, cliptype,
|
||||
subject_fill_type, clip_fill_type);
|
||||
++current_lm;
|
||||
}
|
||||
}
|
||||
@ -504,24 +382,15 @@ void insert_horizontal_local_minima_into_ABL(T const top_y,
|
||||
clip_type cliptype,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
while (current_lm != minima_sorted.end() && top_y == (*current_lm)->y &&
|
||||
(*current_lm)->minimum_has_horizontal) {
|
||||
while (current_lm != minima_sorted.end() && top_y == (*current_lm)->y && (*current_lm)->minimum_has_horizontal) {
|
||||
initialize_lm<T>(current_lm);
|
||||
auto& left_bound = (*current_lm)->left_bound;
|
||||
auto& right_bound = (*current_lm)->right_bound;
|
||||
if (left_bound.edges.empty() && !right_bound.edges.empty()) {
|
||||
insert_lm_only_one_bound(right_bound, active_bounds, rings, scanbeam, cliptype,
|
||||
subject_fill_type, clip_fill_type);
|
||||
} else if (right_bound.edges.empty() && !left_bound.edges.empty()) {
|
||||
throw clipper_exception(
|
||||
"There should only be horizontal local minimum on right bounds!");
|
||||
} else {
|
||||
insert_lm_left_and_right_bound(left_bound, right_bound, active_bounds, rings, scanbeam,
|
||||
cliptype, subject_fill_type, clip_fill_type);
|
||||
}
|
||||
insert_lm_left_and_right_bound(left_bound, right_bound, active_bounds, rings, scanbeam, cliptype,
|
||||
subject_fill_type, clip_fill_type);
|
||||
++current_lm;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
277
mapbox/geometry/wagyu/almost_equal.hpp
Normal file
277
mapbox/geometry/wagyu/almost_equal.hpp
Normal file
@ -0,0 +1,277 @@
|
||||
// Copyright 2005, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
//
|
||||
// Authors: wan@google.com (Zhanyong Wan), eefacm@gmail.com (Sean Mcafee)
|
||||
//
|
||||
// The Google C++ Testing Framework (Google Test)
|
||||
|
||||
// This template class serves as a compile-time function from size to
|
||||
// type. It maps a size in bytes to a primitive type with that
|
||||
// size. e.g.
|
||||
//
|
||||
// TypeWithSize<4>::UInt
|
||||
//
|
||||
// is typedef-ed to be unsigned int (unsigned integer made up of 4
|
||||
// bytes).
|
||||
//
|
||||
// Such functionality should belong to STL, but I cannot find it
|
||||
// there.
|
||||
//
|
||||
// Google Test uses this class in the implementation of floating-point
|
||||
// comparison.
|
||||
//
|
||||
// For now it only handles UInt (unsigned int) as that's all Google Test
|
||||
// needs. Other types can be easily added in the future if need
|
||||
// arises.
|
||||
namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
namespace util {
|
||||
|
||||
template <size_t size>
|
||||
class TypeWithSize {
|
||||
public:
|
||||
// This prevents the user from using TypeWithSize<N> with incorrect
|
||||
// values of N.
|
||||
typedef void UInt;
|
||||
};
|
||||
|
||||
// The specialization for size 4.
|
||||
template <>
|
||||
class TypeWithSize<4> {
|
||||
public:
|
||||
// unsigned int has size 4 in both gcc and MSVC.
|
||||
//
|
||||
// As base/basictypes.h doesn't compile on Windows, we cannot use
|
||||
// uint32, uint64, and etc here.
|
||||
typedef int Int;
|
||||
typedef unsigned int UInt;
|
||||
};
|
||||
|
||||
// The specialization for size 8.
|
||||
template <>
|
||||
class TypeWithSize<8> {
|
||||
public:
|
||||
#if GTEST_OS_WINDOWS
|
||||
typedef __int64 Int;
|
||||
typedef unsigned __int64 UInt;
|
||||
#else
|
||||
typedef long long Int; // NOLINT
|
||||
typedef unsigned long long UInt; // NOLINT
|
||||
#endif // GTEST_OS_WINDOWS
|
||||
};
|
||||
|
||||
// This template class represents an IEEE floating-point number
|
||||
// (either single-precision or double-precision, depending on the
|
||||
// template parameters).
|
||||
//
|
||||
// The purpose of this class is to do more sophisticated number
|
||||
// comparison. (Due to round-off error, etc, it's very unlikely that
|
||||
// two floating-points will be equal exactly. Hence a naive
|
||||
// comparison by the == operation often doesn't work.)
|
||||
//
|
||||
// Format of IEEE floating-point:
|
||||
//
|
||||
// The most-significant bit being the leftmost, an IEEE
|
||||
// floating-point looks like
|
||||
//
|
||||
// sign_bit exponent_bits fraction_bits
|
||||
//
|
||||
// Here, sign_bit is a single bit that designates the sign of the
|
||||
// number.
|
||||
//
|
||||
// For float, there are 8 exponent bits and 23 fraction bits.
|
||||
//
|
||||
// For double, there are 11 exponent bits and 52 fraction bits.
|
||||
//
|
||||
// More details can be found at
|
||||
// http://en.wikipedia.org/wiki/IEEE_floating-point_standard.
|
||||
//
|
||||
// Template parameter:
|
||||
//
|
||||
// RawType: the raw floating-point type (either float or double)
|
||||
template <typename RawType>
|
||||
class FloatingPoint {
|
||||
public:
|
||||
// Defines the unsigned integer type that has the same size as the
|
||||
// floating point number.
|
||||
typedef typename TypeWithSize<sizeof(RawType)>::UInt Bits;
|
||||
|
||||
// Constants.
|
||||
|
||||
// # of bits in a number.
|
||||
static const size_t kBitCount = 8 * sizeof(RawType);
|
||||
|
||||
// # of fraction bits in a number.
|
||||
static const size_t kFractionBitCount = std::numeric_limits<RawType>::digits - 1;
|
||||
|
||||
// # of exponent bits in a number.
|
||||
static const size_t kExponentBitCount = kBitCount - 1 - kFractionBitCount;
|
||||
|
||||
// The mask for the sign bit.
|
||||
static const Bits kSignBitMask = static_cast<Bits>(1) << (kBitCount - 1);
|
||||
|
||||
// The mask for the fraction bits.
|
||||
static const Bits kFractionBitMask = ~static_cast<Bits>(0) >> (kExponentBitCount + 1);
|
||||
|
||||
// The mask for the exponent bits.
|
||||
static const Bits kExponentBitMask = ~(kSignBitMask | kFractionBitMask);
|
||||
|
||||
// How many ULP's (Units in the Last Place) we want to tolerate when
|
||||
// comparing two numbers. The larger the value, the more error we
|
||||
// allow. A 0 value means that two numbers must be exactly the same
|
||||
// to be considered equal.
|
||||
//
|
||||
// The maximum error of a single floating-point operation is 0.5
|
||||
// units in the last place. On Intel CPU's, all floating-point
|
||||
// calculations are done with 80-bit precision, while double has 64
|
||||
// bits. Therefore, 4 should be enough for ordinary use.
|
||||
//
|
||||
// See the following article for more details on ULP:
|
||||
// http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm.
|
||||
static const size_t kMaxUlps = 4;
|
||||
|
||||
// Constructs a FloatingPoint from a raw floating-point number.
|
||||
//
|
||||
// On an Intel CPU, passing a non-normalized NAN (Not a Number)
|
||||
// around may change its bits, although the new value is guaranteed
|
||||
// to be also a NAN. Therefore, don't expect this constructor to
|
||||
// preserve the bits in x when x is a NAN.
|
||||
explicit FloatingPoint(const RawType& x) : u_(x) {
|
||||
}
|
||||
|
||||
// Static methods
|
||||
|
||||
// Reinterprets a bit pattern as a floating-point number.
|
||||
//
|
||||
// This function is needed to test the AlmostEquals() method.
|
||||
static RawType ReinterpretBits(const Bits bits) {
|
||||
FloatingPoint fp(0);
|
||||
fp.u_.bits_ = bits;
|
||||
return fp.u_.value_;
|
||||
}
|
||||
|
||||
// Returns the floating-point number that represent positive infinity.
|
||||
static RawType Infinity() {
|
||||
return ReinterpretBits(kExponentBitMask);
|
||||
}
|
||||
|
||||
// Non-static methods
|
||||
|
||||
// Returns the bits that represents this number.
|
||||
const Bits& bits() const {
|
||||
return u_.bits_;
|
||||
}
|
||||
|
||||
// Returns the exponent bits of this number.
|
||||
Bits exponent_bits() const {
|
||||
return kExponentBitMask & u_.bits_;
|
||||
}
|
||||
|
||||
// Returns the fraction bits of this number.
|
||||
Bits fraction_bits() const {
|
||||
return kFractionBitMask & u_.bits_;
|
||||
}
|
||||
|
||||
// Returns the sign bit of this number.
|
||||
Bits sign_bit() const {
|
||||
return kSignBitMask & u_.bits_;
|
||||
}
|
||||
|
||||
// Returns true iff this is NAN (not a number).
|
||||
bool is_nan() const {
|
||||
// It's a NAN if the exponent bits are all ones and the fraction
|
||||
// bits are not entirely zeros.
|
||||
return (exponent_bits() == kExponentBitMask) && (fraction_bits() != 0);
|
||||
}
|
||||
|
||||
// Returns true iff this number is at most kMaxUlps ULP's away from
|
||||
// rhs. In particular, this function:
|
||||
//
|
||||
// - returns false if either number is (or both are) NAN.
|
||||
// - treats really large numbers as almost equal to infinity.
|
||||
// - thinks +0.0 and -0.0 are 0 DLP's apart.
|
||||
bool AlmostEquals(const FloatingPoint& rhs) const {
|
||||
// The IEEE standard says that any comparison operation involving
|
||||
// a NAN must return false.
|
||||
if (is_nan() || rhs.is_nan())
|
||||
return false;
|
||||
|
||||
return DistanceBetweenSignAndMagnitudeNumbers(u_.bits_, rhs.u_.bits_) <= kMaxUlps;
|
||||
}
|
||||
|
||||
private:
|
||||
// The data type used to store the actual floating-point number.
|
||||
union FloatingPointUnion {
|
||||
explicit FloatingPointUnion(RawType val) : value_(val) {
|
||||
}
|
||||
RawType value_; // The raw floating-point number.
|
||||
Bits bits_; // The bits that represent the number.
|
||||
};
|
||||
|
||||
// Converts an integer from the sign-and-magnitude representation to
|
||||
// the biased representation. More precisely, let N be 2 to the
|
||||
// power of (kBitCount - 1), an integer x is represented by the
|
||||
// unsigned number x + N.
|
||||
//
|
||||
// For instance,
|
||||
//
|
||||
// -N + 1 (the most negative number representable using
|
||||
// sign-and-magnitude) is represented by 1;
|
||||
// 0 is represented by N; and
|
||||
// N - 1 (the biggest number representable using
|
||||
// sign-and-magnitude) is represented by 2N - 1.
|
||||
//
|
||||
// Read http://en.wikipedia.org/wiki/Signed_number_representations
|
||||
// for more details on signed number representations.
|
||||
static Bits SignAndMagnitudeToBiased(const Bits& sam) {
|
||||
if (kSignBitMask & sam) {
|
||||
// sam represents a negative number.
|
||||
return ~sam + 1;
|
||||
} else {
|
||||
// sam represents a positive number.
|
||||
return kSignBitMask | sam;
|
||||
}
|
||||
}
|
||||
|
||||
// Given two numbers in the sign-and-magnitude representation,
|
||||
// returns the distance between them as an unsigned number.
|
||||
static Bits DistanceBetweenSignAndMagnitudeNumbers(const Bits& sam1, const Bits& sam2) {
|
||||
const Bits biased1 = SignAndMagnitudeToBiased(sam1);
|
||||
const Bits biased2 = SignAndMagnitudeToBiased(sam2);
|
||||
return (biased1 >= biased2) ? (biased1 - biased2) : (biased2 - biased1);
|
||||
}
|
||||
|
||||
FloatingPointUnion u_;
|
||||
};
|
||||
|
||||
} // namespace util
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
@ -36,6 +36,7 @@ struct bound {
|
||||
bound() noexcept
|
||||
: edges(),
|
||||
current_edge(edges.end()),
|
||||
next_edge(edges.end()),
|
||||
last_point({ 0, 0 }),
|
||||
ring(nullptr),
|
||||
maximum_bound(nullptr),
|
||||
@ -47,10 +48,11 @@ struct bound {
|
||||
poly_type(polygon_type_subject),
|
||||
side(edge_left) {
|
||||
}
|
||||
|
||||
bound(bound<T> && b) noexcept
|
||||
|
||||
bound(bound<T>&& b) noexcept
|
||||
: edges(std::move(b.edges)),
|
||||
current_edge(std::move(b.current_edge)),
|
||||
next_edge(std::move(b.next_edge)),
|
||||
last_point(std::move(b.last_point)),
|
||||
ring(std::move(b.ring)),
|
||||
maximum_bound(std::move(b.maximum_bound)),
|
||||
@ -62,13 +64,15 @@ struct bound {
|
||||
poly_type(std::move(b.poly_type)),
|
||||
side(std::move(b.side)) {
|
||||
}
|
||||
|
||||
bound(bound<T> const& b) = delete;
|
||||
bound<T>& operator=(bound<T> const&) = delete;
|
||||
};
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
template <class charT, class traits, typename T>
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
|
||||
const bound<T>& bnd) {
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out, const bound<T>& bnd) {
|
||||
out << " Bound: " << &bnd << std::endl;
|
||||
out << " current_x: " << bnd.current_x << std::endl;
|
||||
out << " last_point: " << bnd.last_point.x << ", " << bnd.last_point.y << std::endl;
|
||||
@ -90,6 +94,6 @@ inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, t
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
28
mapbox/geometry/wagyu/bubble_sort.hpp
Normal file
28
mapbox/geometry/wagyu/bubble_sort.hpp
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
template <typename It, class Compare, class MethodOnSwap>
|
||||
void bubble_sort(It begin, It end, Compare c, MethodOnSwap m) {
|
||||
if (begin == end) {
|
||||
return;
|
||||
}
|
||||
bool modified = false;
|
||||
auto last = end - 1;
|
||||
do {
|
||||
modified = false;
|
||||
for (auto itr = begin; itr != last; ++itr) {
|
||||
auto next = std::next(itr);
|
||||
if (!c(*itr, *next)) {
|
||||
m(*itr, *next);
|
||||
std::iter_swap(itr, next);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
} while (modified);
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
@ -6,7 +6,6 @@
|
||||
|
||||
#include <mapbox/geometry/wagyu/config.hpp>
|
||||
#include <mapbox/geometry/wagyu/edge.hpp>
|
||||
#include <mapbox/geometry/wagyu/exceptions.hpp>
|
||||
#include <mapbox/geometry/wagyu/util.hpp>
|
||||
|
||||
namespace mapbox {
|
||||
@ -14,42 +13,8 @@ namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
template <typename T>
|
||||
bool build_edge_list(mapbox::geometry::line_string<T> const& path_geometry,
|
||||
edge_list<T>& edges,
|
||||
bool& is_flat) {
|
||||
if (path_geometry.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto itr_next = path_geometry.begin();
|
||||
++itr_next;
|
||||
auto itr = path_geometry.begin();
|
||||
while (itr_next != path_geometry.end()) {
|
||||
if (*itr_next == *itr) {
|
||||
// Duplicate point advance itr_next, but do not
|
||||
// advance itr
|
||||
++itr_next;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_flat && itr_next->y != itr->y) {
|
||||
is_flat = false;
|
||||
}
|
||||
edges.emplace_back(*itr, *itr_next);
|
||||
itr = itr_next;
|
||||
++itr_next;
|
||||
}
|
||||
|
||||
if (edges.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool point_2_is_between_point_1_and_point_3(mapbox::geometry::point<T> const& pt1,
|
||||
mapbox::geometry::point<T> const& pt2,
|
||||
bool point_2_is_between_point_1_and_point_3(mapbox::geometry::point<T> const& pt1,
|
||||
mapbox::geometry::point<T> const& pt2,
|
||||
mapbox::geometry::point<T> const& pt3) {
|
||||
if ((pt1 == pt3) || (pt1 == pt2) || (pt3 == pt2)) {
|
||||
return false;
|
||||
@ -60,9 +25,8 @@ bool point_2_is_between_point_1_and_point_3(mapbox::geometry::point<T> const& pt
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool build_edge_list(mapbox::geometry::linear_ring<T> const& path_geometry, edge_list<T>& edges) {
|
||||
using value_type = T;
|
||||
template <typename T1, typename T2>
|
||||
bool build_edge_list(mapbox::geometry::linear_ring<T2> const& path_geometry, edge_list<T1>& edges) {
|
||||
|
||||
if (path_geometry.size() < 3) {
|
||||
return false;
|
||||
@ -73,8 +37,8 @@ bool build_edge_list(mapbox::geometry::linear_ring<T> const& path_geometry, edge
|
||||
|
||||
auto itr_rev = path_geometry.rbegin();
|
||||
auto itr = path_geometry.begin();
|
||||
mapbox::geometry::point<value_type> pt1 = *itr_rev;
|
||||
mapbox::geometry::point<value_type> pt2 = *itr;
|
||||
mapbox::geometry::point<T2> pt1 = *itr_rev;
|
||||
mapbox::geometry::point<T2> pt2 = *itr;
|
||||
|
||||
// Find next non repeated point going backwards from
|
||||
// end for pt1
|
||||
@ -86,10 +50,10 @@ bool build_edge_list(mapbox::geometry::linear_ring<T> const& path_geometry, edge
|
||||
pt1 = *itr_rev;
|
||||
}
|
||||
++itr;
|
||||
mapbox::geometry::point<value_type> pt3 = *itr;
|
||||
mapbox::geometry::point<T2> pt3 = *itr;
|
||||
auto itr_last = itr_rev.base();
|
||||
mapbox::geometry::point<value_type> front_pt;
|
||||
mapbox::geometry::point<value_type> back_pt;
|
||||
mapbox::geometry::point<T2> front_pt;
|
||||
mapbox::geometry::point<T2> back_pt;
|
||||
while (true) {
|
||||
if (pt3 == pt2) {
|
||||
// Duplicate point advance itr, but do not
|
||||
@ -120,22 +84,24 @@ bool build_edge_list(mapbox::geometry::linear_ring<T> const& path_geometry, edge
|
||||
edges.pop_back(); // remove previous edge (pt1)
|
||||
}
|
||||
if (!edges.empty()) {
|
||||
if (back_pt == edges.back().top) {
|
||||
pt1 = edges.back().bot;
|
||||
auto const& back_top = edges.back().top;
|
||||
if (static_cast<T1>(back_pt.x) == back_top.x && static_cast<T1>(back_pt.y) == back_top.y) {
|
||||
auto const& back_bot = edges.back().bot;
|
||||
pt1 = mapbox::geometry::point<T2>(static_cast<T2>(back_bot.x), static_cast<T2>(back_bot.y));
|
||||
} else {
|
||||
pt1 = edges.back().top;
|
||||
pt1 = mapbox::geometry::point<T2>(static_cast<T2>(back_top.x), static_cast<T2>(back_top.y));
|
||||
}
|
||||
back_pt = pt1;
|
||||
} else {
|
||||
// If this occurs we must look to the back of the
|
||||
// ring for new points.
|
||||
do {
|
||||
while (*itr_rev == pt2) {
|
||||
++itr_rev;
|
||||
if ((itr + 1) == itr_rev.base()) {
|
||||
return false;
|
||||
}
|
||||
pt1 = *itr_rev;
|
||||
} while (pt1 == pt2);
|
||||
}
|
||||
pt1 = *itr_rev;
|
||||
itr_last = itr_rev.base();
|
||||
}
|
||||
continue;
|
||||
@ -212,6 +178,6 @@ bool build_edge_list(mapbox::geometry::linear_ring<T> const& path_geometry, edge
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -9,32 +9,18 @@ namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
template <typename T>
|
||||
bool add_line_string(mapbox::geometry::line_string<T> const& path_geometry,
|
||||
local_minimum_list<T>& minima_list) {
|
||||
bool is_flat = true;
|
||||
edge_list<T> new_edges;
|
||||
new_edges.reserve(path_geometry.size());
|
||||
if (!build_edge_list(path_geometry, new_edges, is_flat) || new_edges.empty()) {
|
||||
return false;
|
||||
}
|
||||
add_line_to_local_minima_list(new_edges, minima_list, polygon_type_subject);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool add_linear_ring(mapbox::geometry::linear_ring<T> const& path_geometry,
|
||||
local_minimum_list<T>& minima_list,
|
||||
template <typename T1, typename T2>
|
||||
bool add_linear_ring(mapbox::geometry::linear_ring<T2> const& path_geometry,
|
||||
local_minimum_list<T1>& minima_list,
|
||||
polygon_type p_type) {
|
||||
edge_list<T> new_edges;
|
||||
edge_list<T1> new_edges;
|
||||
new_edges.reserve(path_geometry.size());
|
||||
if (!build_edge_list(path_geometry, new_edges) || new_edges.empty()) {
|
||||
if (!build_edge_list<T1, T2>(path_geometry, new_edges) || new_edges.empty()) {
|
||||
return false;
|
||||
}
|
||||
add_ring_to_local_minima_list(new_edges, minima_list, p_type);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -3,53 +3,55 @@
|
||||
#include <mapbox/geometry/wagyu/ring.hpp>
|
||||
#include <mapbox/geometry/wagyu/ring_util.hpp>
|
||||
|
||||
#include <mapbox/geometry/multi_polygon.hpp>
|
||||
|
||||
namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
template <typename T>
|
||||
void push_ring_to_polygon(mapbox::geometry::polygon<T>& poly, ring_ptr<T>& r, bool reverse_output) {
|
||||
mapbox::geometry::linear_ring<T> lr;
|
||||
lr.reserve(r->size + 1);
|
||||
template <typename T1, typename T2>
|
||||
void push_ring_to_polygon(mapbox::geometry::polygon<T2>& poly, ring_ptr<T1> r, bool reverse_output) {
|
||||
mapbox::geometry::linear_ring<T2> lr;
|
||||
lr.reserve(r->size() + 1);
|
||||
auto firstPt = r->points;
|
||||
auto ptIt = r->points;
|
||||
if (reverse_output) {
|
||||
do {
|
||||
lr.push_back({ ptIt->x, ptIt->y });
|
||||
lr.emplace_back(static_cast<T2>(ptIt->x), static_cast<T2>(ptIt->y));
|
||||
ptIt = ptIt->next;
|
||||
} while (ptIt != firstPt);
|
||||
} else {
|
||||
do {
|
||||
lr.push_back({ ptIt->x, ptIt->y });
|
||||
lr.emplace_back(static_cast<T2>(ptIt->x), static_cast<T2>(ptIt->y));
|
||||
ptIt = ptIt->prev;
|
||||
} while (ptIt != firstPt);
|
||||
}
|
||||
lr.push_back({ firstPt->x, firstPt->y }); // close the ring
|
||||
lr.emplace_back(firstPt->x, firstPt->y); // close the ring
|
||||
poly.push_back(lr);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void build_result_polygons(std::vector<mapbox::geometry::polygon<T>>& solution,
|
||||
ring_list<T>& rings,
|
||||
template <typename T1, typename T2>
|
||||
void build_result_polygons(mapbox::geometry::multi_polygon<T2>& solution,
|
||||
ring_vector<T1> const& rings,
|
||||
bool reverse_output) {
|
||||
|
||||
for (auto& r : rings) {
|
||||
assert(r->points);
|
||||
std::size_t cnt = point_count(r->points);
|
||||
if ((r->is_open && cnt < 2) || (!r->is_open && cnt < 3)) {
|
||||
for (auto r : rings) {
|
||||
if (r == nullptr) {
|
||||
continue;
|
||||
}
|
||||
assert(r->points);
|
||||
solution.emplace_back();
|
||||
push_ring_to_polygon(solution.back(), r, reverse_output);
|
||||
for (auto& c : r->children) {
|
||||
assert(c->points);
|
||||
cnt = point_count(c->points);
|
||||
if ((c->is_open && cnt < 2) || (!c->is_open && cnt < 3)) {
|
||||
for (auto c : r->children) {
|
||||
if (c == nullptr) {
|
||||
continue;
|
||||
}
|
||||
assert(c->points);
|
||||
push_ring_to_polygon(solution.back(), c, reverse_output);
|
||||
}
|
||||
for (auto& c : r->children) {
|
||||
for (auto c : r->children) {
|
||||
if (c == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (!c->children.empty()) {
|
||||
build_result_polygons(solution, c->children, reverse_output);
|
||||
}
|
||||
@ -57,12 +59,10 @@ void build_result_polygons(std::vector<mapbox::geometry::polygon<T>>& solution,
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void build_result(std::vector<mapbox::geometry::polygon<T>>& solution,
|
||||
ring_manager<T>& rings,
|
||||
bool reverse_output) {
|
||||
template <typename T1, typename T2>
|
||||
void build_result(mapbox::geometry::multi_polygon<T2>& solution, ring_manager<T1> const& rings, bool reverse_output) {
|
||||
build_result_polygons(solution, rings.children, reverse_output);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -3,26 +3,24 @@
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <list>
|
||||
#include <stdexcept>
|
||||
|
||||
// GCC 4.8 missing range std::vector::insert (c++11)
|
||||
#ifdef __GNUC__
|
||||
#if __GNUC__ == 4 && __GNUC_MINOR__ == 8
|
||||
#define GCC_MISSING_VECTOR_RANGE_INSERT
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
enum clip_type : std::uint8_t {
|
||||
clip_type_intersection = 0,
|
||||
clip_type_union,
|
||||
clip_type_difference,
|
||||
clip_type_x_or
|
||||
};
|
||||
enum clip_type : std::uint8_t { clip_type_intersection = 0, clip_type_union, clip_type_difference, clip_type_x_or };
|
||||
|
||||
enum polygon_type : std::uint8_t { polygon_type_subject = 0, polygon_type_clip };
|
||||
|
||||
enum fill_type : std::uint8_t {
|
||||
fill_type_even_odd = 0,
|
||||
fill_type_non_zero,
|
||||
fill_type_positive,
|
||||
fill_type_negative
|
||||
};
|
||||
enum fill_type : std::uint8_t { fill_type_even_odd = 0, fill_type_non_zero, fill_type_positive, fill_type_negative };
|
||||
|
||||
static double const def_arc_tolerance = 0.25;
|
||||
|
||||
@ -47,6 +45,6 @@ enum end_type {
|
||||
|
||||
template <typename T>
|
||||
using maxima_list = std::list<T>;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -28,24 +28,25 @@ struct edge {
|
||||
mapbox::geometry::point<T> top;
|
||||
double dx;
|
||||
|
||||
edge(edge<T> && e) noexcept
|
||||
: bot(std::move(e.bot)),
|
||||
top(std::move(e.top)),
|
||||
dx(std::move(e.dx)) {}
|
||||
edge(edge<T>&& e) noexcept : bot(std::move(e.bot)), top(std::move(e.top)), dx(std::move(e.dx)) {
|
||||
}
|
||||
|
||||
edge& operator=(edge<T> && e) noexcept {
|
||||
edge& operator=(edge<T>&& e) noexcept {
|
||||
bot = std::move(e.bot);
|
||||
top = std::move(e.top);
|
||||
dx = std::move(e.dx);
|
||||
return *this;
|
||||
}
|
||||
|
||||
edge(mapbox::geometry::point<T> const& current, mapbox::geometry::point<T> const& next_pt) noexcept
|
||||
: bot(current), top(current), dx(0.0) {
|
||||
template <typename T2>
|
||||
edge(mapbox::geometry::point<T2> const& current, mapbox::geometry::point<T2> const& next_pt) noexcept
|
||||
: bot(static_cast<T>(current.x), static_cast<T>(current.y)),
|
||||
top(static_cast<T>(current.x), static_cast<T>(current.y)),
|
||||
dx(0.0) {
|
||||
if (current.y >= next_pt.y) {
|
||||
top = next_pt;
|
||||
top = mapbox::geometry::point<T>(static_cast<T>(next_pt.x), static_cast<T>(next_pt.y));
|
||||
} else {
|
||||
bot = next_pt;
|
||||
bot = mapbox::geometry::point<T>(static_cast<T>(next_pt.x), static_cast<T>(next_pt.y));
|
||||
}
|
||||
double dy = static_cast<double>(top.y - bot.y);
|
||||
if (value_is_zero(dy)) {
|
||||
@ -54,7 +55,6 @@ struct edge {
|
||||
dx = static_cast<double>(top.x - bot.x) / dy;
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
@ -68,8 +68,8 @@ using edge_list_itr = typename edge_list<T>::iterator;
|
||||
|
||||
template <typename T>
|
||||
bool slopes_equal(edge<T> const& e1, edge<T> const& e2) {
|
||||
return (e1.top.y - e1.bot.y) * (e2.top.x - e2.bot.x) ==
|
||||
(e1.top.x - e1.bot.x) * (e2.top.y - e2.bot.y);
|
||||
return static_cast<std::int64_t>(e1.top.y - e1.bot.y) * static_cast<std::int64_t>(e2.top.x - e2.bot.x) ==
|
||||
static_cast<std::int64_t>(e1.top.x - e1.bot.x) * static_cast<std::int64_t>(e2.top.y - e2.bot.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@ -82,16 +82,14 @@ inline double get_current_x(edge<T> const& edge, const T current_y) {
|
||||
if (current_y == edge.top.y) {
|
||||
return static_cast<double>(edge.top.x);
|
||||
} else {
|
||||
return static_cast<double>(edge.bot.x) +
|
||||
edge.dx * static_cast<double>(current_y - edge.bot.y);
|
||||
return static_cast<double>(edge.bot.x) + edge.dx * static_cast<double>(current_y - edge.bot.y);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
template <class charT, class traits, typename T>
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
|
||||
const edge<T>& e) {
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out, const edge<T>& e) {
|
||||
out << " Edge: " << std::endl;
|
||||
out << " bot x: " << e.bot.x << " y: " << e.bot.y << std::endl;
|
||||
out << " top x: " << e.top.x << " y: " << e.top.y << std::endl;
|
||||
@ -117,6 +115,6 @@ inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, t
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
class clipper_exception : public std::exception {
|
||||
private:
|
||||
std::string m_descr;
|
||||
|
||||
public:
|
||||
clipper_exception(const char* description) : m_descr(description) {
|
||||
}
|
||||
virtual ~clipper_exception() noexcept {
|
||||
}
|
||||
virtual const char* what() const noexcept {
|
||||
return m_descr.c_str();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
50
mapbox/geometry/wagyu/interrupt.hpp
Normal file
50
mapbox/geometry/wagyu/interrupt.hpp
Normal file
@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* To enable this by the program, define USE_WAGYU_INTERRUPT before including wagyu.hpp
|
||||
* To request an interruption, call `interrupt_request()`. As soon as Wagyu detects the request
|
||||
* it will raise an exception (`std::runtime_error`).
|
||||
*/
|
||||
|
||||
#ifdef USE_WAGYU_INTERRUPT
|
||||
|
||||
namespace {
|
||||
thread_local bool WAGYU_INTERRUPT_REQUESTED = false;
|
||||
}
|
||||
|
||||
namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
static void interrupt_reset(void) {
|
||||
WAGYU_INTERRUPT_REQUESTED = false;
|
||||
}
|
||||
|
||||
static void interrupt_request(void) {
|
||||
WAGYU_INTERRUPT_REQUESTED = true;
|
||||
}
|
||||
|
||||
static void interrupt_check(void) {
|
||||
if (WAGYU_INTERRUPT_REQUESTED) {
|
||||
interrupt_reset();
|
||||
throw std::runtime_error("Wagyu interrupted");
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
||||
#else /* ! USE_WAGYU_INTERRUPT */
|
||||
|
||||
namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
static void interrupt_check(void) {
|
||||
}
|
||||
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
||||
#endif /* USE_WAGYU_INTERRUPT */
|
@ -17,25 +17,22 @@ namespace wagyu {
|
||||
template <typename T>
|
||||
struct intersect_node {
|
||||
|
||||
active_bound_list_itr<T> bound1;
|
||||
active_bound_list_itr<T> bound2;
|
||||
bound_ptr<T> bound1;
|
||||
bound_ptr<T> bound2;
|
||||
mapbox::geometry::point<double> pt;
|
||||
|
||||
intersect_node(intersect_node<T> && n)
|
||||
: bound1(std::move(n.bound1)),
|
||||
bound2(std::move(n.bound2)),
|
||||
pt(std::move(n.pt)) { }
|
||||
intersect_node(intersect_node<T>&& n) noexcept
|
||||
: bound1(std::move(n.bound1)), bound2(std::move(n.bound2)), pt(std::move(n.pt)) {
|
||||
}
|
||||
|
||||
intersect_node& operator=(intersect_node<T> && n) {
|
||||
intersect_node& operator=(intersect_node<T>&& n) noexcept {
|
||||
bound1 = std::move(n.bound1);
|
||||
bound2 = std::move(n.bound2);
|
||||
pt = std::move(n.pt);
|
||||
return *this;
|
||||
}
|
||||
|
||||
intersect_node(active_bound_list_itr<T> const& bound1_,
|
||||
active_bound_list_itr<T> const& bound2_,
|
||||
mapbox::geometry::point<double> const& pt_)
|
||||
intersect_node(bound_ptr<T> const& bound1_, bound_ptr<T> const& bound2_, mapbox::geometry::point<double> const& pt_)
|
||||
: bound1(bound1_), bound2(bound2_), pt(pt_) {
|
||||
}
|
||||
};
|
||||
@ -50,9 +47,9 @@ inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, t
|
||||
const intersect_node<T>& e) {
|
||||
out << " point x: " << e.pt.x << " y: " << e.pt.y << std::endl;
|
||||
out << " bound 1: " << std::endl;
|
||||
out << *(*e.bound1) << std::endl;
|
||||
out << *e.bound1 << std::endl;
|
||||
out << " bound 2: " << std::endl;
|
||||
out << *(*e.bound2) << std::endl;
|
||||
out << *e.bound2 << std::endl;
|
||||
return out;
|
||||
}
|
||||
|
||||
@ -68,6 +65,6 @@ inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, t
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -2,11 +2,14 @@
|
||||
|
||||
#include <mapbox/geometry/wagyu/active_bound_list.hpp>
|
||||
#include <mapbox/geometry/wagyu/bound.hpp>
|
||||
#include <mapbox/geometry/wagyu/bubble_sort.hpp>
|
||||
#include <mapbox/geometry/wagyu/config.hpp>
|
||||
#include <mapbox/geometry/wagyu/intersect.hpp>
|
||||
#include <mapbox/geometry/wagyu/ring_util.hpp>
|
||||
#include <mapbox/geometry/wagyu/util.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
@ -17,8 +20,8 @@ struct intersect_list_sorter {
|
||||
if (!values_are_equal(node2.pt.y, node1.pt.y)) {
|
||||
return node2.pt.y < node1.pt.y;
|
||||
} else {
|
||||
return ((*node2.bound1)->winding_count2 + (*node2.bound2)->winding_count2) >
|
||||
((*node1.bound1)->winding_count2 + (*node1.bound2)->winding_count2);
|
||||
return (node2.bound1->winding_count2 + node2.bound2->winding_count2) >
|
||||
(node1.bound1->winding_count2 + node1.bound2->winding_count2);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -43,9 +46,7 @@ inline void swap_sides(bound<T>& b1, bound<T>& b2) {
|
||||
}
|
||||
|
||||
template <typename T1, typename T2>
|
||||
bool get_edge_intersection(edge<T1> const& e1,
|
||||
edge<T1> const& e2,
|
||||
mapbox::geometry::point<T2>& pt) {
|
||||
bool get_edge_intersection(edge<T1> const& e1, edge<T1> const& e2, mapbox::geometry::point<T2>& pt) {
|
||||
T2 p0_x = static_cast<T2>(e1.bot.x);
|
||||
T2 p0_y = static_cast<T2>(e1.bot.y);
|
||||
T2 p1_x = static_cast<T2>(e1.top.x);
|
||||
@ -60,145 +61,102 @@ bool get_edge_intersection(edge<T1> const& e1,
|
||||
s2_x = p3_x - p2_x;
|
||||
s2_y = p3_y - p2_y;
|
||||
|
||||
T2 s, 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);
|
||||
T2 s = (-s1_y * (p0_x - p2_x) + s1_x * (p0_y - p2_y)) / (-s2_x * s1_y + s1_x * s2_y);
|
||||
T2 t = (s2_x * (p0_y - p2_y) - s2_y * (p0_x - p2_x)) / (-s2_x * s1_y + s1_x * s2_y);
|
||||
|
||||
if (s >= 0.0 && s <= 1.0 && t >= 0.0 && t <= 1.0) {
|
||||
pt.x = p0_x + (t * s1_x);
|
||||
pt.y = p0_y + (t * s1_y);
|
||||
return true;
|
||||
}
|
||||
// LCOV_EXCL_START
|
||||
return false;
|
||||
// LCOV_EXCL_END
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void build_intersect_list(active_bound_list<T>& active_bounds,
|
||||
intersect_list<T>& intersects) {
|
||||
// bubblesort ...
|
||||
bool isModified = false;
|
||||
do {
|
||||
isModified = false;
|
||||
auto bnd = active_bounds.begin();
|
||||
auto bnd_next = std::next(bnd);
|
||||
while (bnd_next != active_bounds.end()) {
|
||||
if ((*bnd)->current_x > (*bnd_next)->current_x &&
|
||||
!slopes_equal(*((*bnd)->current_edge), *((*bnd_next)->current_edge))) {
|
||||
mapbox::geometry::point<double> pt;
|
||||
if (!get_edge_intersection<T, double>(*((*bnd)->current_edge),
|
||||
*((*bnd_next)->current_edge), pt)) {
|
||||
throw std::runtime_error(
|
||||
"Trying to find intersection of lines that do not intersect");
|
||||
}
|
||||
intersects.emplace_back(bnd, bnd_next, pt);
|
||||
swap_positions_in_ABL(bnd, bnd_next, active_bounds);
|
||||
bnd_next = std::next(bnd);
|
||||
isModified = true;
|
||||
} else {
|
||||
bnd = bnd_next;
|
||||
++bnd_next;
|
||||
}
|
||||
struct intersection_compare {
|
||||
bool operator()(bound_ptr<T> const& b1, bound_ptr<T> const& b2) {
|
||||
return !(b1->current_x > b2->current_x && !slopes_equal(*(b1->current_edge), *(b2->current_edge)));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct on_intersection_swap {
|
||||
|
||||
intersect_list<T>& intersects;
|
||||
|
||||
on_intersection_swap(intersect_list<T>& i) : intersects(i) {
|
||||
}
|
||||
|
||||
void operator()(bound_ptr<T> const& b1, bound_ptr<T> const& b2) {
|
||||
mapbox::geometry::point<double> pt;
|
||||
if (!get_edge_intersection<T, double>(*(b1->current_edge), *(b2->current_edge), pt)) {
|
||||
// LCOV_EXCL_START
|
||||
throw std::runtime_error("Trying to find intersection of lines that do not intersect");
|
||||
// LCOV_EXCL_END
|
||||
}
|
||||
} while (isModified);
|
||||
intersects.emplace_back(b1, b2, pt);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void build_intersect_list(active_bound_list<T>& active_bounds, intersect_list<T>& intersects) {
|
||||
bubble_sort(active_bounds.begin(), active_bounds.end(), intersection_compare<T>(),
|
||||
on_intersection_swap<T>(intersects));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void intersect_bounds(active_bound_list_itr<T>& b1,
|
||||
active_bound_list_itr<T>& b2,
|
||||
void intersect_bounds(bound<T>& b1,
|
||||
bound<T>& b2,
|
||||
mapbox::geometry::point<T> const& pt,
|
||||
clip_type cliptype,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type,
|
||||
ring_manager<T>& rings,
|
||||
active_bound_list<T>& active_bounds) {
|
||||
bool b1Contributing = ((*b1)->ring != nullptr);
|
||||
bool b2Contributing = ((*b2)->ring != nullptr);
|
||||
// if either bound is on an OPEN path ...
|
||||
if ((*b1)->winding_delta == 0 || (*b2)->winding_delta == 0) {
|
||||
// ignore subject-subject open path intersections UNLESS they
|
||||
// are both open paths, AND they are both 'contributing maximas' ...
|
||||
if ((*b1)->winding_delta == 0 && (*b2)->winding_delta == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if intersecting a subj line with a subj poly ...
|
||||
else if ((*b1)->poly_type == (*b2)->poly_type &&
|
||||
(*b1)->winding_delta != (*b2)->winding_delta && cliptype == clip_type_union) {
|
||||
if ((*b1)->winding_delta == 0) {
|
||||
if (b2Contributing) {
|
||||
add_point(b1, active_bounds, pt, rings);
|
||||
if (b1Contributing) {
|
||||
(*b1)->ring = nullptr;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (b1Contributing) {
|
||||
add_point(b2, active_bounds, pt, rings);
|
||||
if (b2Contributing) {
|
||||
(*b2)->ring = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ((*b1)->poly_type != (*b2)->poly_type) {
|
||||
// toggle subj open path index on/off when std::abs(clip.WndCnt) == 1
|
||||
if (((*b1)->winding_delta == 0) && std::abs(static_cast<int>((*b2)->winding_count)) == 1 &&
|
||||
(cliptype != clip_type_union || (*b2)->winding_count2 == 0)) {
|
||||
add_point(b1, active_bounds, pt, rings);
|
||||
if (b1Contributing) {
|
||||
(*b1)->ring = nullptr;
|
||||
}
|
||||
} else if (((*b2)->winding_delta == 0) && (std::abs(static_cast<int>((*b1)->winding_count)) == 1) &&
|
||||
(cliptype != clip_type_union || (*b1)->winding_count2 == 0)) {
|
||||
add_point(b2, active_bounds, pt, rings);
|
||||
if (b2Contributing) {
|
||||
(*b2)->ring = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
bool b1Contributing = (b1.ring != nullptr);
|
||||
bool b2Contributing = (b2.ring != nullptr);
|
||||
|
||||
// update winding counts...
|
||||
// assumes that b1 will be to the Right of b2 ABOVE the intersection
|
||||
if ((*b1)->poly_type == (*b2)->poly_type) {
|
||||
if (is_even_odd_fill_type(*(*b1), subject_fill_type, clip_fill_type)) {
|
||||
std::int32_t oldE1winding_count = (*b1)->winding_count;
|
||||
(*b1)->winding_count = (*b2)->winding_count;
|
||||
(*b2)->winding_count = oldE1winding_count;
|
||||
if (b1.poly_type == b2.poly_type) {
|
||||
if (is_even_odd_fill_type(b1, subject_fill_type, clip_fill_type)) {
|
||||
std::swap(b1.winding_count, b2.winding_count);
|
||||
} else {
|
||||
if ((*b1)->winding_count + (*b2)->winding_delta == 0) {
|
||||
(*b1)->winding_count = -(*b1)->winding_count;
|
||||
if (b1.winding_count + b2.winding_delta == 0) {
|
||||
b1.winding_count = -b1.winding_count;
|
||||
} else {
|
||||
(*b1)->winding_count += (*b2)->winding_delta;
|
||||
b1.winding_count += b2.winding_delta;
|
||||
}
|
||||
if ((*b2)->winding_count - (*b1)->winding_delta == 0) {
|
||||
(*b2)->winding_count = -(*b2)->winding_count;
|
||||
if (b2.winding_count - b1.winding_delta == 0) {
|
||||
b2.winding_count = -b2.winding_count;
|
||||
} else {
|
||||
(*b2)->winding_count -= (*b1)->winding_delta;
|
||||
b2.winding_count -= b1.winding_delta;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!is_even_odd_fill_type(*(*b2), subject_fill_type, clip_fill_type)) {
|
||||
(*b1)->winding_count2 += (*b2)->winding_delta;
|
||||
if (!is_even_odd_fill_type(b2, subject_fill_type, clip_fill_type)) {
|
||||
b1.winding_count2 += b2.winding_delta;
|
||||
} else {
|
||||
(*b1)->winding_count2 = ((*b1)->winding_count2 == 0) ? 1 : 0;
|
||||
b1.winding_count2 = (b1.winding_count2 == 0) ? 1 : 0;
|
||||
}
|
||||
if (!is_even_odd_fill_type(*(*b1), subject_fill_type, clip_fill_type)) {
|
||||
(*b2)->winding_count2 -= (*b1)->winding_delta;
|
||||
if (!is_even_odd_fill_type(b1, subject_fill_type, clip_fill_type)) {
|
||||
b2.winding_count2 -= b1.winding_delta;
|
||||
} else {
|
||||
(*b2)->winding_count2 = ((*b2)->winding_count2 == 0) ? 1 : 0;
|
||||
b2.winding_count2 = (b2.winding_count2 == 0) ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
fill_type b1FillType, b2FillType, b1FillType2, b2FillType2;
|
||||
if ((*b1)->poly_type == polygon_type_subject) {
|
||||
if (b1.poly_type == polygon_type_subject) {
|
||||
b1FillType = subject_fill_type;
|
||||
b1FillType2 = clip_fill_type;
|
||||
} else {
|
||||
b1FillType = clip_fill_type;
|
||||
b1FillType2 = subject_fill_type;
|
||||
}
|
||||
if ((*b2)->poly_type == polygon_type_subject) {
|
||||
if (b2.poly_type == polygon_type_subject) {
|
||||
b2FillType = subject_fill_type;
|
||||
b2FillType2 = clip_fill_type;
|
||||
} else {
|
||||
@ -209,51 +167,51 @@ void intersect_bounds(active_bound_list_itr<T>& b1,
|
||||
std::int32_t b1Wc, b2Wc;
|
||||
switch (b1FillType) {
|
||||
case fill_type_positive:
|
||||
b1Wc = (*b1)->winding_count;
|
||||
b1Wc = b1.winding_count;
|
||||
break;
|
||||
case fill_type_negative:
|
||||
b1Wc = -(*b1)->winding_count;
|
||||
b1Wc = -b1.winding_count;
|
||||
break;
|
||||
case fill_type_even_odd:
|
||||
case fill_type_non_zero:
|
||||
default:
|
||||
b1Wc = std::abs(static_cast<int>((*b1)->winding_count));
|
||||
b1Wc = std::abs(static_cast<int>(b1.winding_count));
|
||||
}
|
||||
switch (b2FillType) {
|
||||
case fill_type_positive:
|
||||
b2Wc = (*b2)->winding_count;
|
||||
b2Wc = b2.winding_count;
|
||||
break;
|
||||
case fill_type_negative:
|
||||
b2Wc = -(*b2)->winding_count;
|
||||
b2Wc = -b2.winding_count;
|
||||
break;
|
||||
case fill_type_even_odd:
|
||||
case fill_type_non_zero:
|
||||
default:
|
||||
b2Wc = std::abs(static_cast<int>((*b2)->winding_count));
|
||||
b2Wc = std::abs(static_cast<int>(b2.winding_count));
|
||||
}
|
||||
if (b1Contributing && b2Contributing) {
|
||||
if ((b1Wc != 0 && b1Wc != 1) || (b2Wc != 0 && b2Wc != 1) ||
|
||||
((*b1)->poly_type != (*b2)->poly_type && cliptype != clip_type_x_or)) {
|
||||
(b1.poly_type != b2.poly_type && cliptype != clip_type_x_or)) {
|
||||
add_local_maximum_point(b1, b2, pt, rings, active_bounds);
|
||||
} else {
|
||||
add_point(b1, active_bounds, pt, rings);
|
||||
add_point(b2, active_bounds, pt, rings);
|
||||
swap_sides(*(*b1), *(*b2));
|
||||
swap_rings(*(*b1), *(*b2));
|
||||
swap_sides(b1, b2);
|
||||
swap_rings(b1, b2);
|
||||
}
|
||||
} else if (b1Contributing) {
|
||||
if (b2Wc == 0 || b2Wc == 1) {
|
||||
add_point(b1, active_bounds, pt, rings);
|
||||
(*b2)->last_point = pt;
|
||||
swap_sides(*(*b1), *(*b2));
|
||||
swap_rings(*(*b1), *(*b2));
|
||||
b2.last_point = pt;
|
||||
swap_sides(b1, b2);
|
||||
swap_rings(b1, b2);
|
||||
}
|
||||
} else if (b2Contributing) {
|
||||
if (b1Wc == 0 || b1Wc == 1) {
|
||||
(*b1)->last_point = pt;
|
||||
b1.last_point = pt;
|
||||
add_point(b2, active_bounds, pt, rings);
|
||||
swap_sides(*(*b1), *(*b2));
|
||||
swap_rings(*(*b1), *(*b2));
|
||||
swap_sides(b1, b2);
|
||||
swap_rings(b1, b2);
|
||||
}
|
||||
} else if ((b1Wc == 0 || b1Wc == 1) && (b2Wc == 0 || b2Wc == 1)) {
|
||||
// neither bound is currently contributing ...
|
||||
@ -261,30 +219,30 @@ void intersect_bounds(active_bound_list_itr<T>& b1,
|
||||
std::int32_t b1Wc2, b2Wc2;
|
||||
switch (b1FillType2) {
|
||||
case fill_type_positive:
|
||||
b1Wc2 = (*b1)->winding_count2;
|
||||
b1Wc2 = b1.winding_count2;
|
||||
break;
|
||||
case fill_type_negative:
|
||||
b1Wc2 = -(*b1)->winding_count2;
|
||||
b1Wc2 = -b1.winding_count2;
|
||||
break;
|
||||
case fill_type_even_odd:
|
||||
case fill_type_non_zero:
|
||||
default:
|
||||
b1Wc2 = std::abs(static_cast<int>((*b1)->winding_count2));
|
||||
b1Wc2 = std::abs(static_cast<int>(b1.winding_count2));
|
||||
}
|
||||
switch (b2FillType2) {
|
||||
case fill_type_positive:
|
||||
b2Wc2 = (*b2)->winding_count2;
|
||||
b2Wc2 = b2.winding_count2;
|
||||
break;
|
||||
case fill_type_negative:
|
||||
b2Wc2 = -(*b2)->winding_count2;
|
||||
b2Wc2 = -b2.winding_count2;
|
||||
break;
|
||||
case fill_type_even_odd:
|
||||
case fill_type_non_zero:
|
||||
default:
|
||||
b2Wc2 = std::abs(static_cast<int>((*b2)->winding_count2));
|
||||
b2Wc2 = std::abs(static_cast<int>(b2.winding_count2));
|
||||
}
|
||||
|
||||
if ((*b1)->poly_type != (*b2)->poly_type) {
|
||||
if (b1.poly_type != b2.poly_type) {
|
||||
add_local_minimum_point(b1, b2, active_bounds, pt, rings);
|
||||
} else if (b1Wc == 1 && b2Wc == 1) {
|
||||
switch (cliptype) {
|
||||
@ -300,8 +258,8 @@ void intersect_bounds(active_bound_list_itr<T>& b1,
|
||||
}
|
||||
break;
|
||||
case clip_type_difference:
|
||||
if ((((*b1)->poly_type == polygon_type_clip) && (b1Wc2 > 0) && (b2Wc2 > 0)) ||
|
||||
(((*b1)->poly_type == polygon_type_subject) && (b1Wc2 <= 0) && (b2Wc2 <= 0))) {
|
||||
if (((b1.poly_type == polygon_type_clip) && (b1Wc2 > 0) && (b2Wc2 > 0)) ||
|
||||
((b1.poly_type == polygon_type_subject) && (b1Wc2 <= 0) && (b2Wc2 <= 0))) {
|
||||
add_local_minimum_point(b1, b2, active_bounds, pt, rings);
|
||||
}
|
||||
break;
|
||||
@ -309,16 +267,29 @@ void intersect_bounds(active_bound_list_itr<T>& b1,
|
||||
add_local_minimum_point(b1, b2, active_bounds, pt, rings);
|
||||
}
|
||||
} else {
|
||||
swap_sides(*(*b1), *(*b2));
|
||||
swap_sides(b1, b2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool bounds_adjacent(intersect_node<T> const& inode) {
|
||||
return (std::next(inode.bound1) == inode.bound2) || (std::next(inode.bound2) == inode.bound1);
|
||||
bool bounds_adjacent(intersect_node<T> const& inode, bound_ptr<T> next) {
|
||||
return (next == inode.bound2) || (next == inode.bound1);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
struct find_first_bound {
|
||||
bound_ptr<T> b1;
|
||||
bound_ptr<T> b2;
|
||||
|
||||
find_first_bound(intersect_node<T> const& inode) : b1(inode.bound1), b2(inode.bound2) {
|
||||
}
|
||||
|
||||
bool operator()(bound_ptr<T> const& b) {
|
||||
return b == b1 || b == b2;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void process_intersect_list(intersect_list<T>& intersects,
|
||||
clip_type cliptype,
|
||||
@ -327,9 +298,18 @@ void process_intersect_list(intersect_list<T>& intersects,
|
||||
ring_manager<T>& rings,
|
||||
active_bound_list<T>& active_bounds) {
|
||||
for (auto node_itr = intersects.begin(); node_itr != intersects.end(); ++node_itr) {
|
||||
if (!bounds_adjacent(*node_itr)) {
|
||||
auto b1 = std::find_if(active_bounds.begin(), active_bounds.end(), find_first_bound<T>(*node_itr));
|
||||
auto b2 = std::next(b1);
|
||||
if (!bounds_adjacent(*node_itr, *b2)) {
|
||||
auto next_itr = std::next(node_itr);
|
||||
while (next_itr != intersects.end() && !bounds_adjacent(*next_itr)) {
|
||||
while (next_itr != intersects.end()) {
|
||||
auto n1 = std::find_if(active_bounds.begin(), active_bounds.end(), find_first_bound<T>(*next_itr));
|
||||
auto n2 = std::next(n1);
|
||||
if (bounds_adjacent(*next_itr, *n2)) {
|
||||
b1 = n1;
|
||||
b2 = n2;
|
||||
break;
|
||||
}
|
||||
++next_itr;
|
||||
}
|
||||
if (next_itr == intersects.end()) {
|
||||
@ -338,16 +318,16 @@ void process_intersect_list(intersect_list<T>& intersects,
|
||||
std::iter_swap(node_itr, next_itr);
|
||||
}
|
||||
mapbox::geometry::point<T> pt = round_point<T>(node_itr->pt);
|
||||
intersect_bounds(node_itr->bound1, node_itr->bound2, pt, cliptype, subject_fill_type,
|
||||
clip_fill_type, rings, active_bounds);
|
||||
swap_positions_in_ABL(node_itr->bound1, node_itr->bound2, active_bounds);
|
||||
intersect_bounds(*(node_itr->bound1), *(node_itr->bound2), pt, cliptype, subject_fill_type, clip_fill_type,
|
||||
rings, active_bounds);
|
||||
std::iter_swap(b1, b2);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void update_current_x(active_bound_list<T>& active_bounds, T top_y) {
|
||||
std::size_t pos = 0;
|
||||
for (auto & bnd : active_bounds) {
|
||||
for (auto& bnd : active_bounds) {
|
||||
bnd->pos = pos++;
|
||||
bnd->current_x = get_current_x(*bnd->current_edge, top_y);
|
||||
}
|
||||
@ -372,16 +352,14 @@ void process_intersections(T top_y,
|
||||
}
|
||||
|
||||
// Restore order of active bounds list
|
||||
active_bounds.sort([] (bound_ptr<T> const& b1, bound_ptr<T> const& b2) {
|
||||
return b1->pos < b2->pos;
|
||||
});
|
||||
std::stable_sort(active_bounds.begin(), active_bounds.end(),
|
||||
[](bound_ptr<T> const& b1, bound_ptr<T> const& b2) { return b1->pos < b2->pos; });
|
||||
|
||||
// Sort the intersection list
|
||||
std::stable_sort(intersects.begin(), intersects.end(), intersect_list_sorter<T>());
|
||||
|
||||
process_intersect_list(intersects, cliptype, subject_fill_type, clip_fill_type, rings,
|
||||
active_bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
process_intersect_list(intersects, cliptype, subject_fill_type, clip_fill_type, rings, active_bounds);
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -45,8 +45,7 @@ using local_minimum_ptr_list_itr = typename local_minimum_ptr_list<T>::iterator;
|
||||
|
||||
template <typename T>
|
||||
struct local_minimum_sorter {
|
||||
inline bool operator()(local_minimum_ptr<T> const& locMin1,
|
||||
local_minimum_ptr<T> const& locMin2) {
|
||||
inline bool operator()(local_minimum_ptr<T> const& locMin1, local_minimum_ptr<T> const& locMin2) {
|
||||
if (locMin2->y == locMin1->y) {
|
||||
return locMin2->minimum_has_horizontal != locMin1->minimum_has_horizontal &&
|
||||
locMin1->minimum_has_horizontal;
|
||||
@ -113,6 +112,6 @@ std::string output_all_edges(local_minimum_ptr_list<T> const& lms) {
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -1,10 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <mapbox/geometry/wagyu/edge.hpp>
|
||||
#include <mapbox/geometry/wagyu/interrupt.hpp>
|
||||
#include <mapbox/geometry/wagyu/local_minimum.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <mapbox/geometry/wagyu/exceptions.hpp>
|
||||
#include <stdexcept>
|
||||
#endif
|
||||
|
||||
namespace mapbox {
|
||||
@ -40,12 +43,10 @@ void start_list_on_local_maximum(edge_list<T>& edges) {
|
||||
break;
|
||||
}
|
||||
if (!edge_is_horizontal && prev_edge_is_horizontal) {
|
||||
if (y_decreasing_before_last_horizontal &&
|
||||
(edge->top == prev_edge->bot || edge->top == prev_edge->top)) {
|
||||
if (y_decreasing_before_last_horizontal && (edge->top == prev_edge->bot || edge->top == prev_edge->top)) {
|
||||
break;
|
||||
}
|
||||
} else if (!y_decreasing_before_last_horizontal && !prev_edge_is_horizontal &&
|
||||
edge_is_horizontal &&
|
||||
} else if (!y_decreasing_before_last_horizontal && !prev_edge_is_horizontal && edge_is_horizontal &&
|
||||
(prev_edge->top == edge->top || prev_edge->top == edge->bot)) {
|
||||
y_decreasing_before_last_horizontal = true;
|
||||
}
|
||||
@ -82,12 +83,10 @@ bound<T> create_bound_towards_minimum(edge_list<T>& edges) {
|
||||
break;
|
||||
}
|
||||
if (!next_edge_is_horizontal && edge_is_horizontal) {
|
||||
if (y_increasing_before_last_horizontal &&
|
||||
(next_edge->bot == edge->bot || next_edge->bot == edge->top)) {
|
||||
if (y_increasing_before_last_horizontal && (next_edge->bot == edge->bot || next_edge->bot == edge->top)) {
|
||||
break;
|
||||
}
|
||||
} else if (!y_increasing_before_last_horizontal && !edge_is_horizontal &&
|
||||
next_edge_is_horizontal &&
|
||||
} else if (!y_increasing_before_last_horizontal && !edge_is_horizontal && next_edge_is_horizontal &&
|
||||
(edge->bot == next_edge->top || edge->bot == next_edge->bot)) {
|
||||
y_increasing_before_last_horizontal = true;
|
||||
}
|
||||
@ -102,7 +101,7 @@ bound<T> create_bound_towards_minimum(edge_list<T>& edges) {
|
||||
if (next_edge == edges.end()) {
|
||||
std::swap(edges, bnd.edges);
|
||||
} else {
|
||||
bnd.edges.reserve(std::distance(edges.begin(), next_edge));
|
||||
bnd.edges.reserve(static_cast<std::size_t>(std::distance(edges.begin(), next_edge)));
|
||||
std::move(edges.begin(), next_edge, std::back_inserter(bnd.edges));
|
||||
edges.erase(edges.begin(), next_edge);
|
||||
}
|
||||
@ -130,12 +129,10 @@ bound<T> create_bound_towards_maximum(edge_list<T>& edges) {
|
||||
break;
|
||||
}
|
||||
if (!next_edge_is_horizontal && edge_is_horizontal) {
|
||||
if (y_decreasing_before_last_horizontal &&
|
||||
(next_edge->top == edge->bot || next_edge->top == edge->top)) {
|
||||
if (y_decreasing_before_last_horizontal && (next_edge->top == edge->bot || next_edge->top == edge->top)) {
|
||||
break;
|
||||
}
|
||||
} else if (!y_decreasing_before_last_horizontal && !edge_is_horizontal &&
|
||||
next_edge_is_horizontal &&
|
||||
} else if (!y_decreasing_before_last_horizontal && !edge_is_horizontal && next_edge_is_horizontal &&
|
||||
(edge->top == next_edge->top || edge->top == next_edge->bot)) {
|
||||
y_decreasing_before_last_horizontal = true;
|
||||
}
|
||||
@ -147,7 +144,7 @@ bound<T> create_bound_towards_maximum(edge_list<T>& edges) {
|
||||
if (next_edge == edges.end()) {
|
||||
std::swap(bnd.edges, edges);
|
||||
} else {
|
||||
bnd.edges.reserve(std::distance(edges.begin(), next_edge));
|
||||
bnd.edges.reserve(static_cast<std::size_t>(std::distance(edges.begin(), next_edge)));
|
||||
std::move(edges.begin(), next_edge, std::back_inserter(bnd.edges));
|
||||
edges.erase(edges.begin(), next_edge);
|
||||
}
|
||||
@ -198,108 +195,7 @@ void move_horizontals_on_left_to_right(bound<T>& left_bound, bound<T>& right_bou
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_line_to_local_minima_list(edge_list<T>& edges, local_minimum_list<T>& minima_list) {
|
||||
|
||||
if (edges.empty()) {
|
||||
return;
|
||||
}
|
||||
// Adjust the order of the ring so we start on a local maximum
|
||||
// therefore we start right away on a bound.
|
||||
start_list_on_local_maximum(edges);
|
||||
bound_ptr<T> last_maximum = nullptr;
|
||||
while (!edges.empty()) {
|
||||
bool lm_minimum_has_horizontal = false;
|
||||
auto to_minimum = create_bound_towards_minimum(edges);
|
||||
assert(!to_minimum.edges.empty());
|
||||
fix_horizontals(to_minimum);
|
||||
to_minimum.poly_type = polygon_type_subject;
|
||||
to_minimum.maximum_bound = last_maximum;
|
||||
to_minimum.winding_delta = 0;
|
||||
auto to_min_first_non_horizontal = to_minimum.edges.begin();
|
||||
while (to_min_first_non_horizontal != to_minimum.edges.end() &&
|
||||
is_horizontal(*to_min_first_non_horizontal)) {
|
||||
lm_minimum_has_horizontal = true;
|
||||
++to_min_first_non_horizontal;
|
||||
}
|
||||
if (edges.empty()) {
|
||||
if (to_min_first_non_horizontal != to_minimum.edges.end() &&
|
||||
to_min_first_non_horizontal->dx > 0.0) {
|
||||
to_minimum.side = edge_left;
|
||||
bound<T> right_bound;
|
||||
right_bound.winding_delta = 0;
|
||||
right_bound.side = edge_right;
|
||||
right_bound.poly_type = polygon_type_subject;
|
||||
move_horizontals_on_left_to_right(to_minimum, right_bound);
|
||||
auto const& min_front = to_minimum.edges.front();
|
||||
minima_list.emplace_back(std::move(to_minimum), std::move(right_bound), min_front.y,
|
||||
lm_minimum_has_horizontal);
|
||||
if (last_maximum) {
|
||||
last_maximum->maximum_bound = &(minima_list.back().left_bound);
|
||||
last_maximum = nullptr;
|
||||
}
|
||||
} else {
|
||||
to_minimum.side = edge_right;
|
||||
bound<T> left_bound;
|
||||
left_bound.winding_delta = 0;
|
||||
left_bound.side = edge_left;
|
||||
left_bound.poly_type = polygon_type_subject;
|
||||
auto const& min_front = to_minimum.edges.front();
|
||||
minima_list.emplace_back(std::move(left_bound), std::move(to_minimum), min_front.y);
|
||||
if (last_maximum) {
|
||||
last_maximum->maximum_bound = &(minima_list.back().right_bound);
|
||||
last_maximum = nullptr;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
bool minimum_is_left = true;
|
||||
auto to_maximum = create_bound_towards_maximum(edges);
|
||||
assert(!to_maximum.edges.empty());
|
||||
fix_horizontals(to_maximum);
|
||||
auto to_max_first_non_horizontal = to_minimum.edges.begin();
|
||||
while (to_max_first_non_horizontal != to_maximum.edges.end() &&
|
||||
is_horizontal(*to_max_first_non_horizontal)) {
|
||||
lm_minimum_has_horizontal = true;
|
||||
++to_max_first_non_horizontal;
|
||||
}
|
||||
if (to_max_first_non_horizontal != to_maximum.edges.end() &&
|
||||
(to_min_first_non_horizontal == to_minimum.edges.end() ||
|
||||
to_max_first_non_horizontal->dx > to_min_first_non_horizontal->dx)) {
|
||||
minimum_is_left = false;
|
||||
move_horizontals_on_left_to_right(to_maximum, to_minimum);
|
||||
} else {
|
||||
minimum_is_left = true;
|
||||
move_horizontals_on_left_to_right(to_minimum, to_maximum);
|
||||
}
|
||||
auto const& min_front = to_minimum.edges.front();
|
||||
to_maximum.poly_type = polygon_type_subject;
|
||||
to_maximum.winding_delta = 0;
|
||||
if (!minimum_is_left) {
|
||||
to_minimum.side = edge_right;
|
||||
to_maximum.side = edge_left;
|
||||
minima_list.emplace_back(std::move(to_maximum), std::move(to_minimum), min_front.bot.y,
|
||||
lm_minimum_has_horizontal);
|
||||
if (last_maximum) {
|
||||
last_maximum->maximum_bound = &(minima_list.back().right_bound);
|
||||
}
|
||||
last_maximum = &(minima_list.back().left_bound);
|
||||
} else {
|
||||
to_minimum.side = edge_left;
|
||||
to_maximum.side = edge_right;
|
||||
minima_list.emplace_back(std::move(to_minimum), std::move(to_maximum), min_front.bot.y,
|
||||
lm_minimum_has_horizontal);
|
||||
if (last_maximum) {
|
||||
last_maximum->maximum_bound = &(minima_list.back().left_bound);
|
||||
}
|
||||
last_maximum = &(minima_list.back().right_bound);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_ring_to_local_minima_list(edge_list<T>& edges,
|
||||
local_minimum_list<T>& minima_list,
|
||||
polygon_type poly_type) {
|
||||
void add_ring_to_local_minima_list(edge_list<T>& edges, local_minimum_list<T>& minima_list, polygon_type poly_type) {
|
||||
|
||||
if (edges.empty()) {
|
||||
return;
|
||||
@ -311,6 +207,7 @@ void add_ring_to_local_minima_list(edge_list<T>& edges,
|
||||
bound_ptr<T> first_minimum = nullptr;
|
||||
bound_ptr<T> last_maximum = nullptr;
|
||||
while (!edges.empty()) {
|
||||
interrupt_check(); // Check for interruptions
|
||||
bool lm_minimum_has_horizontal = false;
|
||||
auto to_minimum = create_bound_towards_minimum(edges);
|
||||
if (edges.empty()) {
|
||||
@ -322,22 +219,20 @@ void add_ring_to_local_minima_list(edge_list<T>& edges,
|
||||
auto to_max_first_non_horizontal = to_maximum.edges.begin();
|
||||
auto to_min_first_non_horizontal = to_minimum.edges.begin();
|
||||
bool minimum_is_left = true;
|
||||
while (to_max_first_non_horizontal != to_maximum.edges.end() &&
|
||||
is_horizontal(*to_max_first_non_horizontal)) {
|
||||
while (to_max_first_non_horizontal != to_maximum.edges.end() && is_horizontal(*to_max_first_non_horizontal)) {
|
||||
lm_minimum_has_horizontal = true;
|
||||
++to_max_first_non_horizontal;
|
||||
}
|
||||
while (to_min_first_non_horizontal != to_minimum.edges.end() &&
|
||||
is_horizontal(*to_min_first_non_horizontal)) {
|
||||
while (to_min_first_non_horizontal != to_minimum.edges.end() && is_horizontal(*to_min_first_non_horizontal)) {
|
||||
lm_minimum_has_horizontal = true;
|
||||
++to_min_first_non_horizontal;
|
||||
}
|
||||
#ifdef DEBUG
|
||||
|
||||
if (to_max_first_non_horizontal == to_maximum.edges.end() ||
|
||||
to_min_first_non_horizontal == to_minimum.edges.end()) {
|
||||
throw clipper_exception("should not have a horizontal only bound for a ring");
|
||||
throw std::runtime_error("should not have a horizontal only bound for a ring");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (lm_minimum_has_horizontal) {
|
||||
if (to_max_first_non_horizontal->bot.x > to_min_first_non_horizontal->bot.x) {
|
||||
minimum_is_left = true;
|
||||
@ -414,6 +309,6 @@ void initialize_lm(local_minimum_ptr_list_itr<T>& lm) {
|
||||
(*lm)->right_bound.ring = nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -37,17 +37,12 @@ struct point {
|
||||
point_ptr<T> next;
|
||||
point_ptr<T> prev;
|
||||
|
||||
point(point<T> && p)
|
||||
: ring(std::move(p.ring)),
|
||||
x(std::move(p.x)),
|
||||
y(std::move(p.y)),
|
||||
next(std::move(p.next)),
|
||||
prev(std::move(p.prev)) { }
|
||||
|
||||
point() : ring(nullptr), x(0), y(0), prev(this), next(this) {
|
||||
}
|
||||
|
||||
point(T x_, T y_) : ring(nullptr), x(x_), y(y_), next(this), prev(this) {
|
||||
}
|
||||
|
||||
point(ring_ptr<T> ring_, mapbox::geometry::point<T> const& pt)
|
||||
: ring(ring_), x(pt.x), y(pt.y), next(this), prev(this) {
|
||||
}
|
||||
@ -59,6 +54,12 @@ struct point {
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using point_vector = std::vector<point_ptr<T>>;
|
||||
|
||||
template <typename T>
|
||||
using point_vector_itr = typename point_vector<T>::iterator;
|
||||
|
||||
template <typename T>
|
||||
bool operator==(point<T> const& lhs, point<T> const& rhs) {
|
||||
return lhs.x == rhs.x && lhs.y == rhs.y;
|
||||
@ -92,8 +93,7 @@ bool operator!=(point<T> const& lhs, mapbox::geometry::point<T> const& rhs) {
|
||||
#ifdef DEBUG
|
||||
|
||||
template <class charT, class traits, typename T>
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
|
||||
const point<T>& p) {
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out, const point<T>& p) {
|
||||
out << " point at: " << p.x << ", " << p.y;
|
||||
return out;
|
||||
}
|
||||
@ -105,6 +105,6 @@ inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, t
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -6,7 +6,6 @@
|
||||
|
||||
#include <mapbox/geometry/wagyu/config.hpp>
|
||||
#include <mapbox/geometry/wagyu/edge.hpp>
|
||||
#include <mapbox/geometry/wagyu/exceptions.hpp>
|
||||
#include <mapbox/geometry/wagyu/local_minimum.hpp>
|
||||
#include <mapbox/geometry/wagyu/util.hpp>
|
||||
|
||||
@ -24,7 +23,7 @@ active_bound_list_itr<T> process_horizontal_left_to_right(T scanline_y,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
auto horizontal_itr_behind = horz_bound;
|
||||
bool is_open = (*horz_bound)->winding_delta == 0;
|
||||
bool shifted = false;
|
||||
bool is_maxima_edge = is_maxima(horz_bound, scanline_y);
|
||||
auto bound_max_pair = active_bounds.end();
|
||||
if (is_maxima_edge) {
|
||||
@ -32,215 +31,188 @@ active_bound_list_itr<T> process_horizontal_left_to_right(T scanline_y,
|
||||
}
|
||||
|
||||
auto hp_itr = rings.current_hp_itr;
|
||||
while (hp_itr != rings.hot_pixels.end() && (hp_itr->y > scanline_y || (hp_itr->y == scanline_y && hp_itr->x < (*horz_bound)->current_edge->bot.x))) {
|
||||
while (hp_itr != rings.hot_pixels.end() &&
|
||||
(hp_itr->y > scanline_y || (hp_itr->y == scanline_y && hp_itr->x < (*horz_bound)->current_edge->bot.x))) {
|
||||
++hp_itr;
|
||||
}
|
||||
|
||||
|
||||
auto bnd = std::next(horz_bound);
|
||||
|
||||
while (bnd != active_bounds.end()) {
|
||||
if (*bnd == nullptr) {
|
||||
++bnd;
|
||||
continue;
|
||||
}
|
||||
// this code block inserts extra coords into horizontal edges (in output
|
||||
// polygons) wherever hot pixels touch these horizontal edges. This helps
|
||||
//'simplifying' polygons (ie if the Simplify property is set).
|
||||
while (hp_itr != rings.hot_pixels.end() && hp_itr->y == scanline_y && hp_itr->x < std::llround((*bnd)->current_x) &&
|
||||
hp_itr->x < (*horz_bound)->current_edge->top.x) {
|
||||
if ((*horz_bound)->ring && !is_open) {
|
||||
while (hp_itr != rings.hot_pixels.end() && hp_itr->y == scanline_y &&
|
||||
hp_itr->x < wround<T>((*bnd)->current_x) && hp_itr->x < (*horz_bound)->current_edge->top.x) {
|
||||
if ((*horz_bound)->ring) {
|
||||
add_point_to_ring(*(*horz_bound), *hp_itr, rings);
|
||||
}
|
||||
++hp_itr;
|
||||
}
|
||||
|
||||
if ((*bnd)->current_x > static_cast<double>((*horz_bound)->current_edge->top.x)) {
|
||||
if (greater_than((*bnd)->current_x, static_cast<double>((*horz_bound)->current_edge->top.x))) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Also break if we've got to the end of an intermediate horizontal edge ...
|
||||
// nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
|
||||
if (std::llround((*bnd)->current_x) == (*horz_bound)->current_edge->top.x &&
|
||||
if (wround<T>((*bnd)->current_x) == (*horz_bound)->current_edge->top.x &&
|
||||
(*horz_bound)->next_edge != (*horz_bound)->edges.end() &&
|
||||
(*horz_bound)->current_edge->dx < (*horz_bound)->next_edge->dx) {
|
||||
break;
|
||||
}
|
||||
|
||||
// note: may be done multiple times
|
||||
if ((*horz_bound)->ring && !is_open) {
|
||||
add_point_to_ring(*(*horz_bound),
|
||||
mapbox::geometry::point<T>(std::llround((*bnd)->current_x),
|
||||
scanline_y),
|
||||
if ((*horz_bound)->ring) {
|
||||
add_point_to_ring(*(*horz_bound), mapbox::geometry::point<T>(wround<T>((*bnd)->current_x), scanline_y),
|
||||
rings);
|
||||
}
|
||||
|
||||
// OK, so far we're still in range of the horizontal Edge but make sure
|
||||
// we're at the last of consec. horizontals when matching with eMaxPair
|
||||
if (is_maxima_edge && bnd == bound_max_pair) {
|
||||
if ((*horz_bound)->ring) {
|
||||
add_local_maximum_point(horz_bound, bound_max_pair,
|
||||
(*horz_bound)->current_edge->top, rings, active_bounds);
|
||||
if ((*horz_bound)->ring && (*bound_max_pair)->ring) {
|
||||
add_local_maximum_point(*(*horz_bound), *(*bound_max_pair), (*horz_bound)->current_edge->top, rings,
|
||||
active_bounds);
|
||||
}
|
||||
active_bounds.erase(bound_max_pair);
|
||||
auto after_horz = active_bounds.erase(horz_bound);
|
||||
if (horizontal_itr_behind != horz_bound) {
|
||||
return horizontal_itr_behind;
|
||||
} else {
|
||||
return after_horz;
|
||||
*bound_max_pair = nullptr;
|
||||
*horz_bound = nullptr;
|
||||
if (!shifted) {
|
||||
++horizontal_itr_behind;
|
||||
}
|
||||
return horizontal_itr_behind;
|
||||
}
|
||||
|
||||
intersect_bounds(horz_bound, bnd,
|
||||
mapbox::geometry::point<T>(std::llround((*bnd)->current_x),
|
||||
scanline_y),
|
||||
intersect_bounds(*(*horz_bound), *(*bnd), mapbox::geometry::point<T>(wround<T>((*bnd)->current_x), scanline_y),
|
||||
cliptype, subject_fill_type, clip_fill_type, rings, active_bounds);
|
||||
auto next_bnd = std::next(bnd);
|
||||
swap_positions_in_ABL(horz_bound, bnd, active_bounds);
|
||||
if (current_edge_is_horizontal<T>(bnd) && horizontal_itr_behind == horz_bound) {
|
||||
horizontal_itr_behind = bnd;
|
||||
}
|
||||
bnd = next_bnd;
|
||||
std::iter_swap(horz_bound, bnd);
|
||||
horz_bound = bnd;
|
||||
++bnd;
|
||||
shifted = true;
|
||||
} // end while (bnd != active_bounds.end())
|
||||
|
||||
if ((*horz_bound)->ring && !is_open) {
|
||||
if ((*horz_bound)->ring) {
|
||||
while (hp_itr != rings.hot_pixels.end() && hp_itr->y == scanline_y &&
|
||||
hp_itr->x < std::llround((*horz_bound)->current_edge->top.x)) {
|
||||
hp_itr->x < (*horz_bound)->current_edge->top.x) {
|
||||
add_point_to_ring(*(*horz_bound), *hp_itr, rings);
|
||||
++hp_itr;
|
||||
}
|
||||
}
|
||||
|
||||
if ((*horz_bound)->next_edge != (*horz_bound)->edges.end()) {
|
||||
if ((*horz_bound)->ring) {
|
||||
add_point_to_ring(*(*horz_bound), (*horz_bound)->current_edge->top, rings);
|
||||
next_edge_in_bound(horz_bound, scanbeam);
|
||||
|
||||
if ((*horz_bound)->winding_delta == 0) {
|
||||
if (horizontal_itr_behind != horz_bound) {
|
||||
return horizontal_itr_behind;
|
||||
} else {
|
||||
return std::next(horz_bound);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
next_edge_in_bound(horz_bound, scanbeam);
|
||||
}
|
||||
if (horizontal_itr_behind != horz_bound) {
|
||||
return horizontal_itr_behind;
|
||||
} else {
|
||||
return std::next(horz_bound);
|
||||
}
|
||||
} else {
|
||||
if ((*horz_bound)->ring) {
|
||||
add_point_to_ring(*(*horz_bound), (*horz_bound)->current_edge->top, rings);
|
||||
}
|
||||
auto after_horz = active_bounds.erase(horz_bound);
|
||||
if (horizontal_itr_behind != horz_bound) {
|
||||
return horizontal_itr_behind;
|
||||
} else {
|
||||
return after_horz;
|
||||
}
|
||||
if ((*horz_bound)->ring) {
|
||||
add_point_to_ring(*(*horz_bound), (*horz_bound)->current_edge->top, rings);
|
||||
}
|
||||
|
||||
if ((*horz_bound)->next_edge != (*horz_bound)->edges.end()) {
|
||||
next_edge_in_bound(*(*horz_bound), scanbeam);
|
||||
} else {
|
||||
*horz_bound = nullptr;
|
||||
}
|
||||
if (!shifted) {
|
||||
++horizontal_itr_behind;
|
||||
}
|
||||
return horizontal_itr_behind;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
active_bound_list_itr<T> process_horizontal_right_to_left(T scanline_y,
|
||||
active_bound_list_itr<T>& horz_bound,
|
||||
active_bound_list_itr<T>& horz_bound_fwd,
|
||||
active_bound_list<T>& active_bounds,
|
||||
ring_manager<T>& rings,
|
||||
scanbeam_list<T>& scanbeam,
|
||||
clip_type cliptype,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
bool is_open = (*horz_bound)->winding_delta == 0;
|
||||
bool is_maxima_edge = is_maxima(horz_bound, scanline_y);
|
||||
auto bound_max_pair = active_bounds.end();
|
||||
auto next_bnd_itr = std::next(horz_bound_fwd);
|
||||
bool is_maxima_edge = is_maxima(horz_bound_fwd, scanline_y);
|
||||
auto bound_max_pair = active_bounds.rend();
|
||||
if (is_maxima_edge) {
|
||||
bound_max_pair = get_maxima_pair<T>(horz_bound, active_bounds);
|
||||
bound_max_pair = active_bound_list_rev_itr<T>(get_maxima_pair<T>(horz_bound_fwd, active_bounds));
|
||||
--bound_max_pair;
|
||||
}
|
||||
auto hp_itr_fwd = rings.current_hp_itr;
|
||||
while (hp_itr_fwd != rings.hot_pixels.end() && (hp_itr_fwd->y < scanline_y || (hp_itr_fwd->y == scanline_y && hp_itr_fwd->x < (*horz_bound)->current_edge->top.x))) {
|
||||
while (hp_itr_fwd != rings.hot_pixels.end() &&
|
||||
(hp_itr_fwd->y < scanline_y ||
|
||||
(hp_itr_fwd->y == scanline_y && hp_itr_fwd->x < (*horz_bound_fwd)->current_edge->top.x))) {
|
||||
++hp_itr_fwd;
|
||||
}
|
||||
auto hp_itr = hot_pixel_rev_itr<T>(hp_itr_fwd);
|
||||
|
||||
auto bnd = active_bound_list_rev_itr<T>(horz_bound);
|
||||
auto bnd = active_bound_list_rev_itr<T>(horz_bound_fwd);
|
||||
auto horz_bound = std::prev(bnd);
|
||||
while (bnd != active_bounds.rend()) {
|
||||
if (*bnd == nullptr) {
|
||||
++bnd;
|
||||
continue;
|
||||
}
|
||||
// this code block inserts extra coords into horizontal edges (in output
|
||||
// polygons) wherever hot pixels touch these horizontal edges.
|
||||
while (hp_itr != rings.hot_pixels.rend() && hp_itr->y == scanline_y && hp_itr->x > std::llround((*bnd)->current_x) &&
|
||||
hp_itr->x > (*horz_bound)->current_edge->top.x) {
|
||||
if ((*horz_bound)->ring && !is_open) {
|
||||
while (hp_itr != rings.hot_pixels.rend() && hp_itr->y == scanline_y &&
|
||||
hp_itr->x > wround<T>((*bnd)->current_x) && hp_itr->x > (*horz_bound)->current_edge->top.x) {
|
||||
if ((*horz_bound)->ring) {
|
||||
add_point_to_ring(*(*horz_bound), *hp_itr, rings);
|
||||
}
|
||||
++hp_itr;
|
||||
}
|
||||
|
||||
if ((*bnd)->current_x < static_cast<double>((*horz_bound)->current_edge->top.x)) {
|
||||
if (less_than((*bnd)->current_x, static_cast<double>((*horz_bound)->current_edge->top.x))) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Also break if we've got to the end of an intermediate horizontal edge ...
|
||||
// nb: Smaller Dx's are to the right of larger Dx's ABOVE the horizontal.
|
||||
if (std::llround((*bnd)->current_x) == (*horz_bound)->current_edge->top.x &&
|
||||
if (wround<T>((*bnd)->current_x) == (*horz_bound)->current_edge->top.x &&
|
||||
(*horz_bound)->next_edge != (*horz_bound)->edges.end() &&
|
||||
(*horz_bound)->current_edge->dx < (*horz_bound)->next_edge->dx) {
|
||||
break;
|
||||
}
|
||||
|
||||
// note: may be done multiple times
|
||||
if ((*horz_bound)->ring && !is_open) {
|
||||
add_point_to_ring(*(*horz_bound),
|
||||
mapbox::geometry::point<T>(std::llround((*bnd)->current_x),
|
||||
scanline_y),
|
||||
if ((*horz_bound)->ring) {
|
||||
add_point_to_ring(*(*horz_bound), mapbox::geometry::point<T>(wround<T>((*bnd)->current_x), scanline_y),
|
||||
rings);
|
||||
}
|
||||
auto bnd_forward = --(bnd.base());
|
||||
|
||||
// OK, so far we're still in range of the horizontal Edge but make sure
|
||||
// we're at the last of consec. horizontals when matching with eMaxPair
|
||||
if (is_maxima_edge && bnd_forward == bound_max_pair) {
|
||||
if ((*horz_bound)->ring) {
|
||||
add_local_maximum_point(horz_bound, bound_max_pair,
|
||||
(*horz_bound)->current_edge->top, rings, active_bounds);
|
||||
if (is_maxima_edge && bnd == bound_max_pair) {
|
||||
if ((*horz_bound)->ring && (*bound_max_pair)->ring) {
|
||||
add_local_maximum_point(*(*horz_bound), *(*bound_max_pair), (*horz_bound)->current_edge->top, rings,
|
||||
active_bounds);
|
||||
}
|
||||
active_bounds.erase(bound_max_pair);
|
||||
return active_bounds.erase(horz_bound);
|
||||
*bound_max_pair = nullptr;
|
||||
*horz_bound = nullptr;
|
||||
return next_bnd_itr;
|
||||
}
|
||||
|
||||
intersect_bounds(bnd_forward, horz_bound,
|
||||
mapbox::geometry::point<T>(std::llround((*bnd)->current_x),
|
||||
scanline_y),
|
||||
intersect_bounds(*(*bnd), *(*horz_bound), mapbox::geometry::point<T>(wround<T>((*bnd)->current_x), scanline_y),
|
||||
cliptype, subject_fill_type, clip_fill_type, rings, active_bounds);
|
||||
swap_positions_in_ABL(horz_bound, bnd_forward, active_bounds);
|
||||
// Why are we not incrementing the bnd iterator here:
|
||||
// It is because reverse iterators point to a `base()` iterator that is a forward
|
||||
// iterator that is one ahead of the reverse bound. This will always be the horizontal
|
||||
// bound,
|
||||
// so what the reverse bound points to will have changed.
|
||||
std::iter_swap(horz_bound, bnd);
|
||||
horz_bound = bnd;
|
||||
++bnd;
|
||||
} // end while (bnd != active_bounds.rend())
|
||||
|
||||
if ((*horz_bound)->ring && !is_open) {
|
||||
while (hp_itr != rings.hot_pixels.rend() && hp_itr->y == scanline_y && hp_itr->x > (*horz_bound)->current_edge->top.x) {
|
||||
if ((*horz_bound)->ring) {
|
||||
while (hp_itr != rings.hot_pixels.rend() && hp_itr->y == scanline_y &&
|
||||
hp_itr->x > (*horz_bound)->current_edge->top.x) {
|
||||
add_point_to_ring(*(*horz_bound), *hp_itr, rings);
|
||||
++hp_itr;
|
||||
}
|
||||
}
|
||||
if ((*horz_bound)->ring) {
|
||||
add_point_to_ring(*(*horz_bound), (*horz_bound)->current_edge->top, rings);
|
||||
}
|
||||
|
||||
if ((*horz_bound)->next_edge != (*horz_bound)->edges.end()) {
|
||||
if ((*horz_bound)->ring) {
|
||||
add_point_to_ring(*(*horz_bound), (*horz_bound)->current_edge->top, rings);
|
||||
next_edge_in_bound(horz_bound, scanbeam);
|
||||
|
||||
if ((*horz_bound)->winding_delta == 0) {
|
||||
return std::next(horz_bound);
|
||||
}
|
||||
} else {
|
||||
next_edge_in_bound(horz_bound, scanbeam);
|
||||
}
|
||||
return std::next(horz_bound);
|
||||
next_edge_in_bound(*(*horz_bound), scanbeam);
|
||||
} else {
|
||||
if ((*horz_bound)->ring) {
|
||||
add_point_to_ring(*(*horz_bound), (*horz_bound)->current_edge->top, rings);
|
||||
}
|
||||
return active_bounds.erase(horz_bound);
|
||||
*horz_bound = nullptr;
|
||||
}
|
||||
return next_bnd_itr;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@ -253,13 +225,11 @@ active_bound_list_itr<T> process_horizontal(T scanline_y,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
if ((*horz_bound)->current_edge->bot.x < (*horz_bound)->current_edge->top.x) {
|
||||
return process_horizontal_left_to_right(scanline_y, horz_bound, active_bounds, rings,
|
||||
scanbeam, cliptype, subject_fill_type,
|
||||
clip_fill_type);
|
||||
return process_horizontal_left_to_right(scanline_y, horz_bound, active_bounds, rings, scanbeam, cliptype,
|
||||
subject_fill_type, clip_fill_type);
|
||||
} else {
|
||||
return process_horizontal_right_to_left(scanline_y, horz_bound, active_bounds, rings,
|
||||
scanbeam, cliptype, subject_fill_type,
|
||||
clip_fill_type);
|
||||
return process_horizontal_right_to_left(scanline_y, horz_bound, active_bounds, rings, scanbeam, cliptype,
|
||||
subject_fill_type, clip_fill_type);
|
||||
}
|
||||
}
|
||||
|
||||
@ -272,14 +242,15 @@ void process_horizontals(T scanline_y,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
for (auto bnd_itr = active_bounds.begin(); bnd_itr != active_bounds.end();) {
|
||||
if (current_edge_is_horizontal<T>(bnd_itr)) {
|
||||
bnd_itr = process_horizontal(scanline_y, bnd_itr, active_bounds, rings, scanbeam,
|
||||
cliptype, subject_fill_type, clip_fill_type);
|
||||
if (*bnd_itr != nullptr && current_edge_is_horizontal<T>(bnd_itr)) {
|
||||
bnd_itr = process_horizontal(scanline_y, bnd_itr, active_bounds, rings, scanbeam, cliptype,
|
||||
subject_fill_type, clip_fill_type);
|
||||
} else {
|
||||
++bnd_itr;
|
||||
}
|
||||
}
|
||||
active_bounds.erase(std::remove(active_bounds.begin(), active_bounds.end(), nullptr), active_bounds.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -3,7 +3,7 @@
|
||||
#include <mapbox/geometry/wagyu/active_bound_list.hpp>
|
||||
#include <mapbox/geometry/wagyu/config.hpp>
|
||||
#include <mapbox/geometry/wagyu/edge.hpp>
|
||||
#include <mapbox/geometry/wagyu/exceptions.hpp>
|
||||
#include <mapbox/geometry/wagyu/interrupt.hpp>
|
||||
#include <mapbox/geometry/wagyu/intersect_util.hpp>
|
||||
#include <mapbox/geometry/wagyu/local_minimum.hpp>
|
||||
#include <mapbox/geometry/wagyu/local_minimum_util.hpp>
|
||||
@ -23,45 +23,35 @@ active_bound_list_itr<T> do_maxima(active_bound_list_itr<T>& bnd,
|
||||
clip_type cliptype,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type,
|
||||
ring_manager<T>& rings,
|
||||
ring_manager<T>& manager,
|
||||
active_bound_list<T>& active_bounds) {
|
||||
if (bndMaxPair == active_bounds.end()) {
|
||||
if ((*bnd)->ring) {
|
||||
add_point_to_ring(*(*bnd), (*bnd)->current_edge->top, rings);
|
||||
}
|
||||
return active_bounds.erase(bnd);
|
||||
}
|
||||
auto bnd_next = std::next(bnd);
|
||||
auto return_bnd = bnd_next;
|
||||
auto return_bnd = bnd;
|
||||
bool skipped = false;
|
||||
while (bnd_next != active_bounds.end() && bnd_next != bndMaxPair) {
|
||||
if (*bnd_next == nullptr) {
|
||||
++bnd_next;
|
||||
continue;
|
||||
}
|
||||
skipped = true;
|
||||
intersect_bounds(bnd, bnd_next, (*bnd)->current_edge->top, cliptype, subject_fill_type,
|
||||
clip_fill_type, rings, active_bounds);
|
||||
swap_positions_in_ABL(bnd, bnd_next, active_bounds);
|
||||
bnd_next = std::next(bnd);
|
||||
intersect_bounds(*(*bnd), *(*bnd_next), (*bnd)->current_edge->top, cliptype, subject_fill_type, clip_fill_type,
|
||||
manager, active_bounds);
|
||||
std::iter_swap(bnd, bnd_next);
|
||||
bnd = bnd_next;
|
||||
++bnd_next;
|
||||
}
|
||||
|
||||
if (!(*bnd)->ring && !(*bndMaxPair)->ring) {
|
||||
active_bounds.erase(bndMaxPair);
|
||||
} else if ((*bnd)->ring && (*bndMaxPair)->ring) {
|
||||
add_local_maximum_point(bnd, bndMaxPair, (*bnd)->current_edge->top, rings, active_bounds);
|
||||
active_bounds.erase(bndMaxPair);
|
||||
} else if ((*bnd)->winding_delta == 0 && (*bnd)->ring) {
|
||||
add_point_to_ring(*(*bnd), (*bnd)->current_edge->top, rings);
|
||||
active_bounds.erase(bndMaxPair);
|
||||
} else if ((*bnd)->winding_delta == 0 && (*bndMaxPair)->ring) {
|
||||
add_point_to_ring(*(*bndMaxPair), (*bnd)->current_edge->top, rings);
|
||||
active_bounds.erase(bndMaxPair);
|
||||
} else {
|
||||
throw clipper_exception("DoMaxima error");
|
||||
if ((*bnd)->ring && (*bndMaxPair)->ring) {
|
||||
add_local_maximum_point(*(*bnd), *(*bndMaxPair), (*bnd)->current_edge->top, manager, active_bounds);
|
||||
} else if ((*bnd)->ring || (*bndMaxPair)->ring) {
|
||||
throw std::runtime_error("DoMaxima error");
|
||||
}
|
||||
auto prev_itr = active_bounds.erase(bnd);
|
||||
if (skipped) {
|
||||
return return_bnd;
|
||||
} else {
|
||||
return prev_itr;
|
||||
*bndMaxPair = nullptr;
|
||||
*bnd = nullptr;
|
||||
if (!skipped) {
|
||||
++return_bnd;
|
||||
}
|
||||
return return_bnd;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@ -70,25 +60,28 @@ void process_edges_at_top_of_scanbeam(T top_y,
|
||||
scanbeam_list<T>& scanbeam,
|
||||
local_minimum_ptr_list<T> const& minima_sorted,
|
||||
local_minimum_ptr_list_itr<T>& current_lm,
|
||||
ring_manager<T>& rings,
|
||||
ring_manager<T>& manager,
|
||||
clip_type cliptype,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
|
||||
for (auto bnd = active_bounds.begin(); bnd != active_bounds.end();) {
|
||||
interrupt_check(); // Check for interruptions
|
||||
if (*bnd == nullptr) {
|
||||
++bnd;
|
||||
continue;
|
||||
}
|
||||
// 1. Process maxima, treating them as if they are "bent" horizontal edges,
|
||||
// but exclude maxima with horizontal edges.
|
||||
|
||||
|
||||
bool is_maxima_edge = is_maxima(bnd, top_y);
|
||||
|
||||
if (is_maxima_edge) {
|
||||
auto bnd_max_pair = get_maxima_pair(bnd, active_bounds);
|
||||
is_maxima_edge = ((bnd_max_pair == active_bounds.end() ||
|
||||
!current_edge_is_horizontal<T>(bnd_max_pair)) &&
|
||||
is_maxima_edge = ((bnd_max_pair == active_bounds.end() || !current_edge_is_horizontal<T>(bnd_max_pair)) &&
|
||||
is_maxima(bnd_max_pair, top_y));
|
||||
if (is_maxima_edge) {
|
||||
bnd = do_maxima(bnd, bnd_max_pair, cliptype, subject_fill_type, clip_fill_type, rings,
|
||||
active_bounds);
|
||||
bnd = do_maxima(bnd, bnd_max_pair, cliptype, subject_fill_type, clip_fill_type, manager, active_bounds);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -96,38 +89,35 @@ void process_edges_at_top_of_scanbeam(T top_y,
|
||||
// 2. Promote horizontal edges.
|
||||
if (is_intermediate(bnd, top_y) && next_edge_is_horizontal<T>(bnd)) {
|
||||
if ((*bnd)->ring) {
|
||||
insert_hot_pixels_in_path(*(*bnd), (*bnd)->current_edge->top, rings, false);
|
||||
insert_hot_pixels_in_path(*(*bnd), (*bnd)->current_edge->top, manager, false);
|
||||
}
|
||||
next_edge_in_bound(bnd, scanbeam);
|
||||
next_edge_in_bound(*(*bnd), scanbeam);
|
||||
if ((*bnd)->ring) {
|
||||
add_point_to_ring(*(*bnd), (*bnd)->current_edge->bot, rings);
|
||||
add_point_to_ring(*(*bnd), (*bnd)->current_edge->bot, manager);
|
||||
}
|
||||
} else {
|
||||
(*bnd)->current_x = get_current_x(*((*bnd)->current_edge), top_y);
|
||||
}
|
||||
|
||||
++bnd;
|
||||
}
|
||||
active_bounds.erase(std::remove(active_bounds.begin(), active_bounds.end(), nullptr), active_bounds.end());
|
||||
|
||||
insert_horizontal_local_minima_into_ABL(top_y, minima_sorted, current_lm, active_bounds, rings,
|
||||
scanbeam, cliptype, subject_fill_type, clip_fill_type);
|
||||
insert_horizontal_local_minima_into_ABL(top_y, minima_sorted, current_lm, active_bounds, manager, scanbeam,
|
||||
cliptype, subject_fill_type, clip_fill_type);
|
||||
|
||||
process_horizontals(top_y, active_bounds, rings, scanbeam, cliptype, subject_fill_type,
|
||||
clip_fill_type);
|
||||
process_horizontals(top_y, active_bounds, manager, scanbeam, cliptype, subject_fill_type, clip_fill_type);
|
||||
|
||||
// 4. Promote intermediate vertices
|
||||
|
||||
for (auto bnd = active_bounds.begin(); bnd != active_bounds.end(); ++bnd) {
|
||||
if (is_intermediate(bnd, top_y)) {
|
||||
if ((*bnd)->ring) {
|
||||
add_point_to_ring(*(*bnd), (*bnd)->current_edge->top, rings);
|
||||
insert_hot_pixels_in_path(*(*bnd), (*bnd)->current_edge->top, rings, false);
|
||||
add_point_to_ring(*(*bnd), (*bnd)->current_edge->top, manager);
|
||||
}
|
||||
next_edge_in_bound(bnd, scanbeam);
|
||||
next_edge_in_bound(*(*bnd), scanbeam);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
139
mapbox/geometry/wagyu/quick_clip.hpp
Normal file
139
mapbox/geometry/wagyu/quick_clip.hpp
Normal file
@ -0,0 +1,139 @@
|
||||
#pragma once
|
||||
|
||||
#include <mapbox/geometry/box.hpp>
|
||||
#include <mapbox/geometry/multi_polygon.hpp>
|
||||
#include <mapbox/geometry/polygon.hpp>
|
||||
#include <mapbox/geometry/wagyu/wagyu.hpp>
|
||||
|
||||
namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
namespace quick_clip {
|
||||
|
||||
template <typename T>
|
||||
mapbox::geometry::point<T> intersect(mapbox::geometry::point<T> a,
|
||||
mapbox::geometry::point<T> b,
|
||||
size_t edge,
|
||||
mapbox::geometry::box<T> const& box) {
|
||||
switch (edge) {
|
||||
case 0:
|
||||
return mapbox::geometry::point<T>(
|
||||
mapbox::geometry::wagyu::wround<T>(static_cast<double>(a.x) + static_cast<double>(b.x - a.x) *
|
||||
static_cast<double>(box.min.y - a.y) /
|
||||
static_cast<double>(b.y - a.y)),
|
||||
box.min.y);
|
||||
|
||||
case 1:
|
||||
return mapbox::geometry::point<T>(
|
||||
box.max.x,
|
||||
mapbox::geometry::wagyu::wround<T>(static_cast<double>(a.y) + static_cast<double>(b.y - a.y) *
|
||||
static_cast<double>(box.max.x - a.x) /
|
||||
static_cast<double>(b.x - a.x)));
|
||||
|
||||
case 2:
|
||||
return mapbox::geometry::point<T>(
|
||||
mapbox::geometry::wagyu::wround<T>(static_cast<double>(a.x) + static_cast<double>(b.x - a.x) *
|
||||
static_cast<double>(box.max.y - a.y) /
|
||||
static_cast<double>(b.y - a.y)),
|
||||
box.max.y);
|
||||
|
||||
default: // case 3
|
||||
return mapbox::geometry::point<T>(
|
||||
box.min.x,
|
||||
mapbox::geometry::wagyu::wround<T>(static_cast<double>(a.y) + static_cast<double>(b.y - a.y) *
|
||||
static_cast<double>(box.min.x - a.x) /
|
||||
static_cast<double>(b.x - a.x)));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool inside(mapbox::geometry::point<T> p, size_t edge, mapbox::geometry::box<T> const& b) {
|
||||
switch (edge) {
|
||||
case 0:
|
||||
return p.y > b.min.y;
|
||||
|
||||
case 1:
|
||||
return p.x < b.max.x;
|
||||
|
||||
case 2:
|
||||
return p.y < b.max.y;
|
||||
|
||||
default: // case 3
|
||||
return p.x > b.min.x;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
mapbox::geometry::linear_ring<T> quick_lr_clip(mapbox::geometry::linear_ring<T> const& ring,
|
||||
mapbox::geometry::box<T> const& b) {
|
||||
mapbox::geometry::linear_ring<T> out = ring;
|
||||
|
||||
for (size_t edge = 0; edge < 4; edge++) {
|
||||
if (out.size() > 0) {
|
||||
mapbox::geometry::linear_ring<T> in = out;
|
||||
mapbox::geometry::point<T> S = in[in.size() - 1];
|
||||
out.resize(0);
|
||||
|
||||
for (size_t e = 0; e < in.size(); e++) {
|
||||
mapbox::geometry::point<T> E = in[e];
|
||||
|
||||
if (inside(E, edge, b)) {
|
||||
if (!inside(S, edge, b)) {
|
||||
out.push_back(intersect(S, E, edge, b));
|
||||
}
|
||||
out.push_back(E);
|
||||
} else if (inside(S, edge, b)) {
|
||||
out.push_back(intersect(S, E, edge, b));
|
||||
}
|
||||
|
||||
S = E;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (out.size() < 3) {
|
||||
out.clear();
|
||||
return out;
|
||||
}
|
||||
// Close the ring if the first/last point was outside
|
||||
if (out[0] != out[out.size() - 1]) {
|
||||
out.push_back(out[0]);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
} // namespace quick_clip
|
||||
|
||||
template <typename T>
|
||||
mapbox::geometry::multi_polygon<T>
|
||||
clip(mapbox::geometry::polygon<T> const& poly, mapbox::geometry::box<T> const& b, fill_type subject_fill_type) {
|
||||
mapbox::geometry::multi_polygon<T> result;
|
||||
wagyu<T> clipper;
|
||||
for (auto const& lr : poly) {
|
||||
auto new_lr = quick_clip::quick_lr_clip(lr, b);
|
||||
if (!new_lr.empty()) {
|
||||
clipper.add_ring(new_lr, polygon_type_subject);
|
||||
}
|
||||
}
|
||||
clipper.execute(clip_type_union, result, subject_fill_type, fill_type_even_odd);
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
mapbox::geometry::multi_polygon<T>
|
||||
clip(mapbox::geometry::multi_polygon<T> const& mp, mapbox::geometry::box<T> const& b, fill_type subject_fill_type) {
|
||||
mapbox::geometry::multi_polygon<T> result;
|
||||
wagyu<T> clipper;
|
||||
for (auto const& poly : mp) {
|
||||
for (auto const& lr : poly) {
|
||||
auto new_lr = quick_clip::quick_lr_clip(lr, b);
|
||||
if (!new_lr.empty()) {
|
||||
clipper.add_ring(new_lr, polygon_type_subject);
|
||||
}
|
||||
}
|
||||
}
|
||||
clipper.execute(clip_type_union, result, subject_fill_type, fill_type_even_odd);
|
||||
return result;
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
@ -2,13 +2,14 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <cmath>
|
||||
#include <deque>
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <mapbox/geometry/box.hpp>
|
||||
#include <mapbox/geometry/wagyu/point.hpp>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <vector>
|
||||
#include <deque>
|
||||
#include <mapbox/geometry/wagyu/point.hpp>
|
||||
|
||||
#ifdef DEBUG
|
||||
#include <execinfo.h>
|
||||
@ -29,37 +30,117 @@ namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
template <typename T>
|
||||
double area_from_point(point_ptr<T> op, std::size_t& size, mapbox::geometry::box<T>& bbox) {
|
||||
point_ptr<T> startOp = op;
|
||||
size = 0;
|
||||
double a = 0.0;
|
||||
T min_x = op->x;
|
||||
T max_x = op->x;
|
||||
T min_y = op->y;
|
||||
T max_y = op->y;
|
||||
do {
|
||||
++size;
|
||||
if (op->x > max_x) {
|
||||
max_x = op->x;
|
||||
} else if (op->x < min_x) {
|
||||
min_x = op->x;
|
||||
}
|
||||
if (op->y > max_y) {
|
||||
max_y = op->y;
|
||||
} else if (op->y < min_y) {
|
||||
min_y = op->y;
|
||||
}
|
||||
a += static_cast<double>(op->prev->x + op->x) * static_cast<double>(op->prev->y - op->y);
|
||||
op = op->next;
|
||||
} while (op != startOp);
|
||||
bbox.min.x = min_x;
|
||||
bbox.max.x = max_x;
|
||||
bbox.min.y = min_y;
|
||||
bbox.max.y = max_y;
|
||||
return a * 0.5;
|
||||
}
|
||||
|
||||
// NOTE: ring and ring_ptr are forward declared in wagyu/point.hpp
|
||||
|
||||
template <typename T>
|
||||
using ring_vector = std::vector<ring_ptr<T>>;
|
||||
|
||||
template <typename T>
|
||||
using ring_list = std::list<ring_ptr<T>>;
|
||||
|
||||
template <typename T>
|
||||
struct ring {
|
||||
std::size_t ring_index; // To support unset 0 is undefined and indexes offset by 1
|
||||
std::size_t size;
|
||||
double area;
|
||||
|
||||
std::size_t size_; // number of points in the ring
|
||||
double area_; // area of the ring
|
||||
mapbox::geometry::box<T> bbox; // bounding box of the ring
|
||||
|
||||
ring_ptr<T> parent;
|
||||
ring_list<T> children;
|
||||
ring_vector<T> children;
|
||||
|
||||
point_ptr<T> points;
|
||||
point_ptr<T> bottom_point;
|
||||
bool is_open;
|
||||
bool is_hole_;
|
||||
bool corrected;
|
||||
|
||||
ring( ring const& ) = delete;
|
||||
ring& operator=(ring const& ) = delete;
|
||||
ring(ring const&) = delete;
|
||||
ring& operator=(ring const&) = delete;
|
||||
|
||||
ring()
|
||||
: ring_index(0),
|
||||
size(0),
|
||||
area(std::numeric_limits<double>::quiet_NaN()),
|
||||
size_(0),
|
||||
area_(std::numeric_limits<double>::quiet_NaN()),
|
||||
bbox({ 0, 0 }, { 0, 0 }),
|
||||
parent(nullptr),
|
||||
children(),
|
||||
points(nullptr),
|
||||
bottom_point(nullptr),
|
||||
is_open(false) {
|
||||
is_hole_(false),
|
||||
corrected(false) {
|
||||
}
|
||||
|
||||
void reset_stats() {
|
||||
area_ = std::numeric_limits<double>::quiet_NaN();
|
||||
is_hole_ = false;
|
||||
bbox.min.x = 0;
|
||||
bbox.min.y = 0;
|
||||
bbox.max.x = 0;
|
||||
bbox.max.y = 0;
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
void recalculate_stats() {
|
||||
if (points != nullptr) {
|
||||
area_ = area_from_point(points, size_, bbox);
|
||||
is_hole_ = !(area_ > 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
void set_stats(double a, std::size_t s, mapbox::geometry::box<T> const& b) {
|
||||
bbox = b;
|
||||
area_ = a;
|
||||
size_ = s;
|
||||
is_hole_ = !(area_ > 0.0);
|
||||
}
|
||||
|
||||
double area() {
|
||||
if (std::isnan(area_)) {
|
||||
recalculate_stats();
|
||||
}
|
||||
return area_;
|
||||
}
|
||||
|
||||
bool is_hole() {
|
||||
if (std::isnan(area_)) {
|
||||
recalculate_stats();
|
||||
}
|
||||
return is_hole_;
|
||||
}
|
||||
|
||||
std::size_t size() {
|
||||
if (std::isnan(area_)) {
|
||||
recalculate_stats();
|
||||
}
|
||||
return size_;
|
||||
}
|
||||
};
|
||||
|
||||
@ -74,9 +155,9 @@ using hot_pixel_rev_itr = typename hot_pixel_vector<T>::reverse_iterator;
|
||||
|
||||
template <typename T>
|
||||
struct ring_manager {
|
||||
|
||||
ring_list<T> children;
|
||||
std::vector<point_ptr<T>> all_points;
|
||||
|
||||
ring_vector<T> children;
|
||||
point_vector<T> all_points;
|
||||
hot_pixel_vector<T> hot_pixels;
|
||||
hot_pixel_itr<T> current_hp_itr;
|
||||
std::deque<point<T>> points;
|
||||
@ -84,8 +165,8 @@ struct ring_manager {
|
||||
std::vector<point<T>> storage;
|
||||
std::size_t index;
|
||||
|
||||
ring_manager( ring_manager const& ) = delete;
|
||||
ring_manager& operator=(ring_manager const& ) = delete;
|
||||
ring_manager(ring_manager const&) = delete;
|
||||
ring_manager& operator=(ring_manager const&) = delete;
|
||||
|
||||
ring_manager()
|
||||
: children(),
|
||||
@ -106,16 +187,15 @@ void preallocate_point_memory(ring_manager<T>& rings, std::size_t size) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ring_ptr<T> create_new_ring(ring_manager<T>& rings) {
|
||||
rings.rings.emplace_back();
|
||||
ring_ptr<T> result = &rings.rings.back();
|
||||
result->ring_index = rings.index++;
|
||||
ring_ptr<T> create_new_ring(ring_manager<T>& manager) {
|
||||
manager.rings.emplace_back();
|
||||
ring_ptr<T> result = &manager.rings.back();
|
||||
result->ring_index = manager.index++;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
point_ptr<T>
|
||||
create_new_point(ring_ptr<T> r, mapbox::geometry::point<T> const& pt, ring_manager<T>& rings) {
|
||||
point_ptr<T> create_new_point(ring_ptr<T> r, mapbox::geometry::point<T> const& pt, ring_manager<T>& rings) {
|
||||
point_ptr<T> point;
|
||||
if (rings.storage.size() < rings.storage.capacity()) {
|
||||
rings.storage.emplace_back(r, pt);
|
||||
@ -146,81 +226,181 @@ point_ptr<T> create_new_point(ring_ptr<T> r,
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ring1_child_of_ring2(ring_ptr<T> ring1, ring_ptr<T> ring2, ring_manager<T>& manager) {
|
||||
assert(ring1 != ring2);
|
||||
if (ring1->parent == ring2) {
|
||||
return;
|
||||
void set_to_children(ring_ptr<T> r, ring_vector<T>& children) {
|
||||
for (auto& c : children) {
|
||||
if (c == nullptr) {
|
||||
c = r;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (ring1->parent == nullptr) {
|
||||
manager.children.remove(ring1);
|
||||
} else {
|
||||
ring1->parent->children.remove(ring1);
|
||||
}
|
||||
if (ring2 == nullptr) {
|
||||
ring1->parent = nullptr;
|
||||
manager.children.push_back(ring1);
|
||||
} else {
|
||||
ring1->parent = ring2;
|
||||
ring2->children.push_back(ring1);
|
||||
children.push_back(r);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void remove_from_children(ring_ptr<T> r, ring_vector<T>& children) {
|
||||
for (auto& c : children) {
|
||||
if (c == r) {
|
||||
c = nullptr;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ring1_sibling_of_ring2(ring_ptr<T> ring1, ring_ptr<T> ring2, ring_manager<T>& manager) {
|
||||
assert(ring1 != ring2);
|
||||
if (ring1->parent == ring2->parent) {
|
||||
void assign_as_child(ring_ptr<T> new_ring, ring_ptr<T> parent, ring_manager<T>& manager) {
|
||||
// Assigning as a child assumes that this is
|
||||
// a brand new ring. Therefore it does
|
||||
// not have any existing relationships
|
||||
if ((parent == nullptr && new_ring->is_hole()) || (parent != nullptr && new_ring->is_hole() == parent->is_hole())) {
|
||||
throw std::runtime_error("Trying to assign a child that is the same orientation as the parent");
|
||||
}
|
||||
auto& children = parent == nullptr ? manager.children : parent->children;
|
||||
set_to_children(new_ring, children);
|
||||
new_ring->parent = parent;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void reassign_as_child(ring_ptr<T> ring, ring_ptr<T> parent, ring_manager<T>& manager) {
|
||||
// Reassigning a ring assumes it already
|
||||
// has an existing parent
|
||||
if ((parent == nullptr && ring->is_hole()) || (parent != nullptr && ring->is_hole() == parent->is_hole())) {
|
||||
throw std::runtime_error("Trying to re-assign a child that is the same orientation as the parent");
|
||||
}
|
||||
|
||||
// Remove the old child relationship
|
||||
auto& old_children = ring->parent == nullptr ? manager.children : ring->parent->children;
|
||||
remove_from_children(ring, old_children);
|
||||
|
||||
// Add new child relationship
|
||||
auto& children = parent == nullptr ? manager.children : parent->children;
|
||||
set_to_children(ring, children);
|
||||
ring->parent = parent;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void assign_as_sibling(ring_ptr<T> new_ring, ring_ptr<T> sibling, ring_manager<T>& manager) {
|
||||
// Assigning as a sibling assumes that this is
|
||||
// a brand new ring. Therefore it does
|
||||
// not have any existing relationships
|
||||
if (new_ring->is_hole() != sibling->is_hole()) {
|
||||
throw std::runtime_error("Trying to assign to be a sibling that is not the same orientation as the sibling");
|
||||
}
|
||||
auto& children = sibling->parent == nullptr ? manager.children : sibling->parent->children;
|
||||
set_to_children(new_ring, children);
|
||||
new_ring->parent = sibling->parent;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void reassign_as_sibling(ring_ptr<T> ring, ring_ptr<T> sibling, ring_manager<T>& manager) {
|
||||
if (ring->parent == sibling->parent) {
|
||||
return;
|
||||
}
|
||||
if (ring1->parent == nullptr) {
|
||||
manager.children.remove(ring1);
|
||||
} else {
|
||||
ring1->parent->children.remove(ring1);
|
||||
// Assigning as a sibling assumes that this is
|
||||
// a brand new ring. Therefore it does
|
||||
// not have any existing relationships
|
||||
if (ring->is_hole() != sibling->is_hole()) {
|
||||
throw std::runtime_error("Trying to assign to be a sibling that is not the same orientation as the sibling");
|
||||
}
|
||||
if (ring2->parent == nullptr) {
|
||||
manager.children.push_back(ring1);
|
||||
} else {
|
||||
ring2->parent->children.push_back(ring1);
|
||||
}
|
||||
ring1->parent = ring2->parent;
|
||||
// Remove the old child relationship
|
||||
auto& old_children = ring->parent == nullptr ? manager.children : ring->parent->children;
|
||||
remove_from_children(ring, old_children);
|
||||
// Add new relationship
|
||||
auto& children = sibling->parent == nullptr ? manager.children : sibling->parent->children;
|
||||
set_to_children(ring, children);
|
||||
ring->parent = sibling->parent;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ring1_replaces_ring2(ring_ptr<T> ring1, ring_ptr<T> ring2, ring_manager<T>& manager) {
|
||||
assert(ring1 != ring2);
|
||||
if (ring2->parent == nullptr) {
|
||||
manager.children.remove(ring2);
|
||||
} else {
|
||||
ring2->parent->children.remove(ring2);
|
||||
}
|
||||
auto& ring1_children = ring1 == nullptr ? manager.children : ring1->children;
|
||||
for (auto& c : ring2->children) {
|
||||
if (c == nullptr) {
|
||||
continue;
|
||||
}
|
||||
c->parent = ring1;
|
||||
set_to_children(c, ring1_children);
|
||||
c = nullptr;
|
||||
}
|
||||
if (ring1 == nullptr) {
|
||||
manager.children.splice(manager.children.end(), ring2->children);
|
||||
} else {
|
||||
ring1->children.splice(ring1->children.end(), ring2->children);
|
||||
}
|
||||
ring2->parent = nullptr;
|
||||
// Remove the old child relationship
|
||||
auto& old_children = ring2->parent == nullptr ? manager.children : ring2->parent->children;
|
||||
remove_from_children(ring2, old_children);
|
||||
ring2->points = nullptr;
|
||||
ring2->reset_stats();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void remove_ring(ring_ptr<T> r, ring_manager<T>& manager) {
|
||||
if (r->parent == nullptr) {
|
||||
manager.children.remove(r);
|
||||
for (auto& c : r->children) {
|
||||
c->parent = nullptr;
|
||||
void remove_points(point_ptr<T> pt) {
|
||||
if (pt != nullptr) {
|
||||
pt->prev->next = nullptr;
|
||||
while (pt != nullptr) {
|
||||
point_ptr<T> tmp = pt;
|
||||
pt = pt->next;
|
||||
tmp->next = nullptr;
|
||||
tmp->prev = nullptr;
|
||||
tmp->ring = nullptr;
|
||||
}
|
||||
manager.children.splice(manager.children.end(), r->children);
|
||||
} else {
|
||||
r->parent->children.remove(r);
|
||||
for (auto& c : r->children) {
|
||||
c->parent = r->parent;
|
||||
}
|
||||
r->parent->children.splice(r->parent->children.end(), r->children);
|
||||
r->parent = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void remove_ring_and_points(ring_ptr<T> r,
|
||||
ring_manager<T>& manager,
|
||||
bool remove_children = true,
|
||||
bool remove_from_parent = true) {
|
||||
// Removes a ring and any children that might be
|
||||
// under that ring.
|
||||
for (auto& c : r->children) {
|
||||
if (c == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (remove_children) {
|
||||
remove_ring_and_points(c, manager, true, false);
|
||||
}
|
||||
c = nullptr;
|
||||
}
|
||||
if (remove_from_parent) {
|
||||
// Remove the old child relationship
|
||||
auto& old_children = r->parent == nullptr ? manager.children : r->parent->children;
|
||||
remove_from_children(r, old_children);
|
||||
}
|
||||
point_ptr<T> pt = r->points;
|
||||
if (pt != nullptr) {
|
||||
pt->prev->next = nullptr;
|
||||
while (pt != nullptr) {
|
||||
point_ptr<T> tmp = pt;
|
||||
pt = pt->next;
|
||||
tmp->next = nullptr;
|
||||
tmp->prev = nullptr;
|
||||
tmp->ring = nullptr;
|
||||
}
|
||||
}
|
||||
r->points = nullptr;
|
||||
r->reset_stats();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void remove_ring(ring_ptr<T> r, ring_manager<T>& manager, bool remove_children = true, bool remove_from_parent = true) {
|
||||
// Removes a ring and any children that might be
|
||||
// under that ring.
|
||||
for (auto& c : r->children) {
|
||||
if (c == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (remove_children) {
|
||||
remove_ring(c, manager, true, false);
|
||||
}
|
||||
c = nullptr;
|
||||
}
|
||||
if (remove_from_parent) {
|
||||
// Remove the old child relationship
|
||||
auto& old_children = r->parent == nullptr ? manager.children : r->parent->children;
|
||||
remove_from_children(r, old_children);
|
||||
}
|
||||
r->points = nullptr;
|
||||
r->reset_stats();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline std::size_t ring_depth(ring_ptr<T> r) {
|
||||
std::size_t depth = 0;
|
||||
@ -236,6 +416,10 @@ inline std::size_t ring_depth(ring_ptr<T> r) {
|
||||
|
||||
template <typename T>
|
||||
inline bool ring_is_hole(ring_ptr<T> r) {
|
||||
// This is different then the "normal" way of determing if
|
||||
// a ring is a hole or not because it uses the depth of the
|
||||
// the ring to determine if it is a hole or not. This is only done
|
||||
// intially when rings are output from Vatti.
|
||||
return ring_depth(r) & 1;
|
||||
}
|
||||
|
||||
@ -265,17 +449,6 @@ void init(const_point_ptr<T>& node) {
|
||||
set_prev(node, node);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::size_t point_count(const const_point_ptr<T>& orig_node) {
|
||||
std::size_t size = 0;
|
||||
point_ptr<T> n = orig_node;
|
||||
do {
|
||||
n = get_next(n);
|
||||
++size;
|
||||
} while (n != orig_node);
|
||||
return size;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void link_before(point_ptr<T>& node, point_ptr<T>& new_node) {
|
||||
point_ptr<T> prev_node = get_prev(node);
|
||||
@ -327,36 +500,10 @@ void reverse_ring(point_ptr<T> pp) {
|
||||
} while (pp1 != pp);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
double area_from_point(point_ptr<T> op, std::size_t & size) {
|
||||
point_ptr<T> startOp = op;
|
||||
size = 1;
|
||||
if (!op) {
|
||||
return 0.0;
|
||||
}
|
||||
double a = 0.0;
|
||||
do {
|
||||
++size;
|
||||
a += static_cast<double>(op->prev->x + op->x) * static_cast<double>(op->prev->y - op->y);
|
||||
op = op->next;
|
||||
} while (op != startOp);
|
||||
return a * 0.5;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
double area(ring_ptr<T> r) {
|
||||
assert(r != nullptr);
|
||||
if (std::isnan(r->area)) {
|
||||
r->area = area_from_point(r->points, r->size);
|
||||
}
|
||||
return r->area;
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
|
||||
template <class charT, class traits, typename T>
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
|
||||
const ring<T>& r) {
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out, ring<T>& r) {
|
||||
out << " ring_index: " << r.ring_index << std::endl;
|
||||
if (!r.parent) {
|
||||
// out << " parent_ring ptr: nullptr" << std::endl;
|
||||
@ -373,7 +520,7 @@ inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, t
|
||||
}
|
||||
auto pt_itr = r.points;
|
||||
if (pt_itr) {
|
||||
out << " area: " << r.area << std::endl;
|
||||
out << " area: " << r.area() << std::endl;
|
||||
out << " points:" << std::endl;
|
||||
out << " [[[" << pt_itr->x << "," << pt_itr->y << "],";
|
||||
pt_itr = pt_itr->next;
|
||||
@ -389,6 +536,22 @@ inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, t
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string debug_ring_addresses(ring_ptr<T> r) {
|
||||
std::ostringstream out;
|
||||
out << "Ring: " << r->ring_index << std::endl;
|
||||
if (r->points == nullptr) {
|
||||
out << " Ring has no points" << std::endl;
|
||||
return out.str();
|
||||
}
|
||||
auto pt_itr = r->points;
|
||||
do {
|
||||
out << " [" << pt_itr->x << "," << pt_itr->y << "] - " << pt_itr << std::endl;
|
||||
pt_itr = pt_itr->next;
|
||||
} while (pt_itr != r->points);
|
||||
return out.str();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
std::string output_as_polygon(ring_ptr<T> r) {
|
||||
std::ostringstream out;
|
||||
@ -404,6 +567,9 @@ std::string output_as_polygon(ring_ptr<T> r) {
|
||||
}
|
||||
out << "[" << pt_itr->x << "," << pt_itr->y << "]]";
|
||||
for (auto const& c : r->children) {
|
||||
if (c == nullptr) {
|
||||
continue;
|
||||
}
|
||||
pt_itr = c->points;
|
||||
if (pt_itr) {
|
||||
out << ",[[" << pt_itr->x << "," << pt_itr->y << "],";
|
||||
@ -424,23 +590,10 @@ std::string output_as_polygon(ring_ptr<T> r) {
|
||||
}
|
||||
|
||||
template <class charT, class traits, typename T>
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
|
||||
const ring_list<T>& rings) {
|
||||
out << "START RING LIST" << std::endl;
|
||||
for (auto& r : rings) {
|
||||
out << " ring: " << r->ring_index << " - " << r << std::endl;
|
||||
out << *r;
|
||||
}
|
||||
out << "END RING LIST" << std::endl;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <class charT, class traits, typename T>
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
|
||||
const ring_vector<T>& rings) {
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out, ring_vector<T>& rings) {
|
||||
out << "START RING VECTOR" << std::endl;
|
||||
for (auto& r : rings) {
|
||||
if (!r->points) {
|
||||
if (r == nullptr || !r->points) {
|
||||
continue;
|
||||
}
|
||||
out << " ring: " << r->ring_index << " - " << r << std::endl;
|
||||
@ -452,7 +605,7 @@ inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, t
|
||||
|
||||
template <class charT, class traits, typename T>
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
|
||||
const std::deque<ring<T>>& rings) {
|
||||
std::deque<ring<T>>& rings) {
|
||||
out << "START RING VECTOR" << std::endl;
|
||||
for (auto& r : rings) {
|
||||
if (!r.points) {
|
||||
@ -467,7 +620,7 @@ inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, t
|
||||
|
||||
template <class charT, class traits, typename T>
|
||||
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
|
||||
const hot_pixel_vector<T>& hp_vec) {
|
||||
hot_pixel_vector<T>& hp_vec) {
|
||||
out << "Hot Pixels: " << std::endl;
|
||||
for (auto& hp : hp_vec) {
|
||||
out << hp << std::endl;
|
||||
@ -475,6 +628,6 @@ inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, t
|
||||
return out;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -15,7 +15,7 @@
|
||||
// free(strs);
|
||||
#endif
|
||||
|
||||
#include <queue>
|
||||
#include <algorithm>
|
||||
|
||||
#include <mapbox/geometry/wagyu/active_bound_list.hpp>
|
||||
#include <mapbox/geometry/wagyu/config.hpp>
|
||||
@ -28,62 +28,38 @@ namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
template <typename T>
|
||||
void set_hole_state(active_bound_list_itr<T>& bnd,
|
||||
active_bound_list<T>& active_bounds,
|
||||
ring_manager<T>& rings) {
|
||||
auto bnd2 = active_bound_list_rev_itr<T>(bnd);
|
||||
void set_hole_state(bound<T>& bnd, active_bound_list<T> const& active_bounds, ring_manager<T>& rings) {
|
||||
auto bnd_itr = std::find(active_bounds.rbegin(), active_bounds.rend(), &bnd);
|
||||
++bnd_itr;
|
||||
bound_ptr<T> bndTmp = nullptr;
|
||||
// Find first non line ring to the left of current bound.
|
||||
while (bnd2 != active_bounds.rend()) {
|
||||
if ((*bnd2)->ring && (*bnd2)->winding_delta != 0) {
|
||||
while (bnd_itr != active_bounds.rend()) {
|
||||
if (*bnd_itr == nullptr) {
|
||||
++bnd_itr;
|
||||
continue;
|
||||
}
|
||||
if ((*bnd_itr)->ring) {
|
||||
if (!bndTmp) {
|
||||
bndTmp = (*bnd2);
|
||||
} else if (bndTmp->ring == (*bnd2)->ring) {
|
||||
bndTmp = (*bnd_itr);
|
||||
} else if (bndTmp->ring == (*bnd_itr)->ring) {
|
||||
bndTmp = nullptr;
|
||||
}
|
||||
}
|
||||
++bnd2;
|
||||
++bnd_itr;
|
||||
}
|
||||
if (!bndTmp) {
|
||||
(*bnd)->ring->parent = nullptr;
|
||||
rings.children.push_back((*bnd)->ring);
|
||||
bnd.ring->parent = nullptr;
|
||||
rings.children.push_back(bnd.ring);
|
||||
} else {
|
||||
(*bnd)->ring->parent = bndTmp->ring;
|
||||
bndTmp->ring->children.push_back((*bnd)->ring);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void set_hole_state(active_bound_list_rev_itr<T>& bnd,
|
||||
active_bound_list<T>& active_bounds,
|
||||
ring_manager<T>& rings) {
|
||||
auto bnd2 = std::next(bnd);
|
||||
bound_ptr<T> bndTmp = nullptr;
|
||||
// Find first non line ring to the left of current bound.
|
||||
while (bnd2 != active_bounds.rend()) {
|
||||
if ((*bnd2)->ring && (*bnd2)->winding_delta != 0) {
|
||||
if (!bndTmp) {
|
||||
bndTmp = (*bnd2);
|
||||
} else if (bndTmp->ring == (*bnd2)->ring) {
|
||||
bndTmp = nullptr;
|
||||
}
|
||||
}
|
||||
++bnd2;
|
||||
}
|
||||
|
||||
if (!bndTmp) {
|
||||
(*bnd)->ring->parent = nullptr;
|
||||
rings.children.push_back((*bnd)->ring);
|
||||
} else {
|
||||
(*bnd)->ring->parent = bndTmp->ring;
|
||||
bndTmp->ring->children.push_back((*bnd)->ring);
|
||||
bnd.ring->parent = bndTmp->ring;
|
||||
bndTmp->ring->children.push_back(bnd.ring);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void update_current_hp_itr(T scanline_y, ring_manager<T>& rings) {
|
||||
while (rings.current_hp_itr->y > scanline_y) {
|
||||
++rings.current_hp_itr;
|
||||
++rings.current_hp_itr;
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,18 +74,17 @@ struct hot_pixel_sorter {
|
||||
}
|
||||
};
|
||||
|
||||
// Due to the nature of floating point calculations
|
||||
// and the high likely hood of values around X.5, we
|
||||
// need to fudge what is X.5 some for our rounding.
|
||||
const double rounding_offset = 1e-12;
|
||||
const double rounding_offset_y = 5e-13;
|
||||
|
||||
template <typename T>
|
||||
T round_towards_min(double val) {
|
||||
// 0.5 rounds to 0
|
||||
// 0.0 rounds to 0
|
||||
// -0.5 rounds to -1
|
||||
return static_cast<T>(std::ceil(val - 0.5 + rounding_offset));
|
||||
double half = std::floor(val) + 0.5;
|
||||
if (values_are_equal(val, half)) {
|
||||
return static_cast<T>(std::floor(val));
|
||||
} else {
|
||||
return static_cast<T>(std::llround(val));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@ -117,7 +92,12 @@ T round_towards_max(double val) {
|
||||
// 0.5 rounds to 1
|
||||
// 0.0 rounds to 0
|
||||
// -0.5 rounds to 0
|
||||
return static_cast<T>(std::floor(val + 0.5 + rounding_offset));
|
||||
double half = std::floor(val) + 0.5;
|
||||
if (values_are_equal(val, half)) {
|
||||
return static_cast<T>(std::ceil(val));
|
||||
} else {
|
||||
return static_cast<T>(std::llround(val));
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@ -141,8 +121,8 @@ inline T get_edge_min_x(edge<T> const& edge, const T current_y) {
|
||||
if (current_y == edge.bot.y) {
|
||||
return edge.bot.x;
|
||||
} else {
|
||||
double return_val = static_cast<double>(edge.bot.x) +
|
||||
edge.dx * (static_cast<double>(current_y - edge.bot.y) + 0.5 - rounding_offset_y);
|
||||
double return_val =
|
||||
static_cast<double>(edge.bot.x) + edge.dx * (static_cast<double>(current_y - edge.bot.y) + 0.5);
|
||||
T value = round_towards_min<T>(return_val);
|
||||
return value;
|
||||
}
|
||||
@ -170,8 +150,8 @@ inline T get_edge_max_x(edge<T> const& edge, const T current_y) {
|
||||
if (current_y == edge.bot.y) {
|
||||
return edge.bot.x;
|
||||
} else {
|
||||
double return_val = static_cast<double>(edge.bot.x) +
|
||||
edge.dx * (static_cast<double>(current_y - edge.bot.y) + 0.5 - rounding_offset_y);
|
||||
double return_val =
|
||||
static_cast<double>(edge.bot.x) + edge.dx * (static_cast<double>(current_y - edge.bot.y) + 0.5);
|
||||
T value = round_towards_max<T>(return_val);
|
||||
return value;
|
||||
}
|
||||
@ -184,14 +164,14 @@ void hot_pixel_set_left_to_right(T y,
|
||||
T end_x,
|
||||
bound<T>& bnd,
|
||||
ring_manager<T>& rings,
|
||||
hot_pixel_itr<T> & itr,
|
||||
hot_pixel_itr<T> & end,
|
||||
hot_pixel_itr<T>& itr,
|
||||
hot_pixel_itr<T>& end,
|
||||
bool add_end_point) {
|
||||
T x_min = get_edge_min_x(*(bnd.current_edge), y);
|
||||
x_min = std::max(x_min, start_x);
|
||||
T x_max = get_edge_max_x(*(bnd.current_edge), y);
|
||||
x_max = std::min(x_max, end_x);
|
||||
for (;itr != end; ++itr) {
|
||||
for (; itr != end; ++itr) {
|
||||
if (itr->x < x_min) {
|
||||
continue;
|
||||
}
|
||||
@ -221,16 +201,16 @@ void hot_pixel_set_right_to_left(T y,
|
||||
T end_x,
|
||||
bound<T>& bnd,
|
||||
ring_manager<T>& rings,
|
||||
hot_pixel_rev_itr<T> & itr,
|
||||
hot_pixel_rev_itr<T> & end,
|
||||
hot_pixel_rev_itr<T>& itr,
|
||||
hot_pixel_rev_itr<T>& end,
|
||||
bool add_end_point) {
|
||||
T x_min = get_edge_min_x(*(bnd.current_edge), y);
|
||||
x_min = std::max(x_min, end_x);
|
||||
T x_max = get_edge_max_x(*(bnd.current_edge), y);
|
||||
x_max = std::min(x_max, start_x);
|
||||
for (;itr != end; ++itr) {
|
||||
for (; itr != end; ++itr) {
|
||||
if (itr->x > x_max) {
|
||||
continue;
|
||||
continue;
|
||||
}
|
||||
if (itr->x < x_min) {
|
||||
break;
|
||||
@ -267,10 +247,6 @@ void insert_hot_pixels_in_path(bound<T>& bnd,
|
||||
if (end_pt == bnd.last_point) {
|
||||
return;
|
||||
}
|
||||
if (!bnd.ring) {
|
||||
bnd.last_point = end_pt;
|
||||
return;
|
||||
}
|
||||
|
||||
T start_y = bnd.last_point.y;
|
||||
T start_x = bnd.last_point.x;
|
||||
@ -297,8 +273,7 @@ void insert_hot_pixels_in_path(bound<T>& bnd,
|
||||
}
|
||||
auto first_itr = hot_pixel_rev_itr<T>(itr);
|
||||
bool add_end_point_itr = (y != end_pt.y || add_end_point);
|
||||
hot_pixel_set_right_to_left(y, start_x, end_x, bnd, rings, first_itr, last_itr,
|
||||
add_end_point_itr);
|
||||
hot_pixel_set_right_to_left(y, start_x, end_x, bnd, rings, first_itr, last_itr, add_end_point_itr);
|
||||
}
|
||||
} else {
|
||||
for (; itr != rings.hot_pixels.end();) {
|
||||
@ -316,8 +291,7 @@ void insert_hot_pixels_in_path(bound<T>& bnd,
|
||||
}
|
||||
auto last_itr = itr;
|
||||
bool add_end_point_itr = (y != end_pt.y || add_end_point);
|
||||
hot_pixel_set_left_to_right(y, start_x, end_x, bnd, rings, first_itr, last_itr,
|
||||
add_end_point_itr);
|
||||
hot_pixel_set_left_to_right(y, start_x, end_x, bnd, rings, first_itr, last_itr, add_end_point_itr);
|
||||
}
|
||||
}
|
||||
bnd.last_point = end_pt;
|
||||
@ -329,41 +303,20 @@ void add_to_hot_pixels(mapbox::geometry::point<T> const& pt, ring_manager<T>& ri
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_first_point(active_bound_list_itr<T>& bnd,
|
||||
void add_first_point(bound<T>& bnd,
|
||||
active_bound_list<T>& active_bounds,
|
||||
mapbox::geometry::point<T> const& pt,
|
||||
ring_manager<T>& rings) {
|
||||
|
||||
ring_ptr<T> r = create_new_ring(rings);
|
||||
(*bnd)->ring = r;
|
||||
r->is_open = ((*bnd)->winding_delta == 0);
|
||||
bnd.ring = r;
|
||||
r->points = create_new_point(r, pt, rings);
|
||||
if (!r->is_open) {
|
||||
set_hole_state(bnd, active_bounds, rings);
|
||||
}
|
||||
(*bnd)->last_point = pt;
|
||||
set_hole_state(bnd, active_bounds, rings);
|
||||
bnd.last_point = pt;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_first_point(active_bound_list_rev_itr<T>& bnd,
|
||||
active_bound_list<T>& active_bounds,
|
||||
mapbox::geometry::point<T> const& pt,
|
||||
ring_manager<T>& rings) {
|
||||
ring_ptr<T> r = create_new_ring(rings);
|
||||
// no ring currently set!
|
||||
(*bnd)->ring = r;
|
||||
r->is_open = ((*bnd)->winding_delta == 0);
|
||||
r->points = create_new_point(r, pt, rings);
|
||||
if (!r->is_open) {
|
||||
set_hole_state(bnd, active_bounds, rings);
|
||||
}
|
||||
(*bnd)->last_point = pt;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_point_to_ring(bound<T>& bnd,
|
||||
mapbox::geometry::point<T> const& pt,
|
||||
ring_manager<T>& rings) {
|
||||
void add_point_to_ring(bound<T>& bnd, mapbox::geometry::point<T> const& pt, ring_manager<T>& rings) {
|
||||
assert(bnd.ring);
|
||||
// Handle hot pixels
|
||||
insert_hot_pixels_in_path(bnd, pt, rings, false);
|
||||
@ -384,64 +337,35 @@ void add_point_to_ring(bound<T>& bnd,
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_point(active_bound_list_itr<T>& bnd,
|
||||
void add_point(bound<T>& bnd,
|
||||
active_bound_list<T>& active_bounds,
|
||||
mapbox::geometry::point<T> const& pt,
|
||||
ring_manager<T>& rings) {
|
||||
if (!(*bnd)->ring) {
|
||||
if (bnd.ring == nullptr) {
|
||||
add_first_point(bnd, active_bounds, pt, rings);
|
||||
} else {
|
||||
add_point_to_ring(*(*bnd), pt, rings);
|
||||
add_point_to_ring(bnd, pt, rings);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_point(active_bound_list_rev_itr<T>& bnd,
|
||||
active_bound_list<T>& active_bounds,
|
||||
mapbox::geometry::point<T> const& pt,
|
||||
ring_manager<T>& rings) {
|
||||
if (!(*bnd)->ring) {
|
||||
add_first_point(bnd, active_bounds, pt, rings);
|
||||
} else {
|
||||
add_point_to_ring(*(*bnd), pt, rings);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_local_minimum_point(active_bound_list_itr<T> b1,
|
||||
active_bound_list_itr<T> b2,
|
||||
void add_local_minimum_point(bound<T>& b1,
|
||||
bound<T>& b2,
|
||||
active_bound_list<T>& active_bounds,
|
||||
mapbox::geometry::point<T> const& pt,
|
||||
ring_manager<T>& rings) {
|
||||
active_bound_list_itr<T> b;
|
||||
active_bound_list_rev_itr<T> prev_bound;
|
||||
active_bound_list_rev_itr<T> prev_b1(b1);
|
||||
active_bound_list_rev_itr<T> prev_b2(b2);
|
||||
if (is_horizontal(*((*b2)->current_edge)) ||
|
||||
((*b1)->current_edge->dx > (*b2)->current_edge->dx)) {
|
||||
if (is_horizontal(*b2.current_edge) || (b1.current_edge->dx > b2.current_edge->dx)) {
|
||||
add_point(b1, active_bounds, pt, rings);
|
||||
(*b2)->last_point = pt;
|
||||
(*b2)->ring = (*b1)->ring;
|
||||
(*b1)->side = edge_left;
|
||||
(*b2)->side = edge_right;
|
||||
b = b1;
|
||||
if (prev_b1 != active_bounds.rend() && std::prev(b) == b2) {
|
||||
prev_bound = prev_b2;
|
||||
} else {
|
||||
prev_bound = prev_b1;
|
||||
}
|
||||
b2.last_point = pt;
|
||||
b2.ring = b1.ring;
|
||||
b1.side = edge_left;
|
||||
b2.side = edge_right;
|
||||
} else {
|
||||
add_point(b2, active_bounds, pt, rings);
|
||||
(*b1)->last_point = pt;
|
||||
(*b1)->ring = (*b2)->ring;
|
||||
(*b1)->side = edge_right;
|
||||
(*b2)->side = edge_left;
|
||||
b = b2;
|
||||
if (prev_b2 != active_bounds.rend() && std::prev(b) == b1) {
|
||||
prev_bound = prev_b1;
|
||||
} else {
|
||||
prev_bound = prev_b2;
|
||||
}
|
||||
b1.last_point = pt;
|
||||
b1.ring = b2.ring;
|
||||
b1.side = edge_right;
|
||||
b2.side = edge_left;
|
||||
}
|
||||
}
|
||||
|
||||
@ -450,7 +374,7 @@ inline double get_dx(point<T> const& pt1, point<T> const& pt2) {
|
||||
if (pt1.y == pt2.y) {
|
||||
return std::numeric_limits<double>::infinity();
|
||||
} else {
|
||||
return static_cast<double>(pt2.x - pt2.x) / static_cast<double>(pt2.y - pt1.y);
|
||||
return static_cast<double>(pt2.x - pt1.x) / static_cast<double>(pt2.y - pt1.y);
|
||||
}
|
||||
}
|
||||
|
||||
@ -483,7 +407,8 @@ bool first_is_bottom_point(const_point_ptr<T> btmPt1, const_point_ptr<T> btmPt2)
|
||||
if (values_are_equal(std::max(dx1p, dx1n), std::max(dx2p, dx2n)) &&
|
||||
values_are_equal(std::min(dx1p, dx1n), std::min(dx2p, dx2n))) {
|
||||
std::size_t s = 0;
|
||||
return area_from_point(btmPt1, s) > 0.0; // if otherwise identical use orientation
|
||||
mapbox::geometry::box<T> bbox({ 0, 0 }, { 0, 0 });
|
||||
return area_from_point(btmPt1, s, bbox) > 0.0; // if otherwise identical use orientation
|
||||
} else {
|
||||
return (greater_than_or_equal(dx1p, dx2p) && greater_than_or_equal(dx1p, dx2n)) ||
|
||||
(greater_than_or_equal(dx1n, dx2p) && greater_than_or_equal(dx1n, dx2n));
|
||||
@ -492,15 +417,15 @@ bool first_is_bottom_point(const_point_ptr<T> btmPt1, const_point_ptr<T> btmPt2)
|
||||
|
||||
template <typename T>
|
||||
point_ptr<T> get_bottom_point(point_ptr<T> pp) {
|
||||
point_ptr<T> dups = 0;
|
||||
point_ptr<T> dups = nullptr;
|
||||
point_ptr<T> p = pp->next;
|
||||
while (p != pp) {
|
||||
if (p->y > pp->y) {
|
||||
pp = p;
|
||||
dups = 0;
|
||||
dups = nullptr;
|
||||
} else if (p->y == pp->y && p->x <= pp->x) {
|
||||
if (p->x < pp->x) {
|
||||
dups = 0;
|
||||
dups = nullptr;
|
||||
pp = p;
|
||||
} else {
|
||||
if (p->next != pp && p->prev != pp) {
|
||||
@ -526,37 +451,37 @@ point_ptr<T> get_bottom_point(point_ptr<T> pp) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
ring_ptr<T> get_lower_most_ring(ring_ptr<T> outRec1, ring_ptr<T> outRec2) {
|
||||
ring_ptr<T> get_lower_most_ring(ring_ptr<T> ring1, ring_ptr<T> ring2) {
|
||||
// work out which polygon fragment has the correct hole state ...
|
||||
if (!outRec1->bottom_point) {
|
||||
outRec1->bottom_point = get_bottom_point(outRec1->points);
|
||||
if (!ring1->bottom_point) {
|
||||
ring1->bottom_point = get_bottom_point(ring1->points);
|
||||
}
|
||||
if (!outRec2->bottom_point) {
|
||||
outRec2->bottom_point = get_bottom_point(outRec2->points);
|
||||
if (!ring2->bottom_point) {
|
||||
ring2->bottom_point = get_bottom_point(ring2->points);
|
||||
}
|
||||
point_ptr<T> OutPt1 = outRec1->bottom_point;
|
||||
point_ptr<T> OutPt2 = outRec2->bottom_point;
|
||||
if (OutPt1->y > OutPt2->y) {
|
||||
return outRec1;
|
||||
} else if (OutPt1->y < OutPt2->y) {
|
||||
return outRec2;
|
||||
} else if (OutPt1->x < OutPt2->x) {
|
||||
return outRec1;
|
||||
} else if (OutPt1->x > OutPt2->x) {
|
||||
return outRec2;
|
||||
} else if (OutPt1->next == OutPt1) {
|
||||
return outRec2;
|
||||
} else if (OutPt2->next == OutPt2) {
|
||||
return outRec1;
|
||||
} else if (first_is_bottom_point(OutPt1, OutPt2)) {
|
||||
return outRec1;
|
||||
point_ptr<T> pt1 = ring1->bottom_point;
|
||||
point_ptr<T> pt2 = ring2->bottom_point;
|
||||
if (pt1->y > pt2->y) {
|
||||
return ring1;
|
||||
} else if (pt1->y < pt2->y) {
|
||||
return ring2;
|
||||
} else if (pt1->x < pt2->x) {
|
||||
return ring1;
|
||||
} else if (pt1->x > pt2->x) {
|
||||
return ring2;
|
||||
} else if (pt1->next == pt1) {
|
||||
return ring2;
|
||||
} else if (pt2->next == pt2) {
|
||||
return ring1;
|
||||
} else if (first_is_bottom_point(pt1, pt2)) {
|
||||
return ring1;
|
||||
} else {
|
||||
return outRec2;
|
||||
return ring2;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool ring1_right_of_ring2(ring_ptr<T> ring1, ring_ptr<T> ring2) {
|
||||
bool ring1_child_below_ring2(ring_ptr<T> ring1, ring_ptr<T> ring2) {
|
||||
do {
|
||||
ring1 = ring1->parent;
|
||||
if (ring1 == ring2) {
|
||||
@ -576,38 +501,35 @@ void update_points_ring(ring_ptr<T> ring) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void append_ring(active_bound_list_itr<T>& b1,
|
||||
active_bound_list_itr<T>& b2,
|
||||
active_bound_list<T>& active_bounds,
|
||||
ring_manager<T>& manager) {
|
||||
void append_ring(bound<T>& b1, bound<T>& b2, active_bound_list<T>& active_bounds, ring_manager<T>& manager) {
|
||||
// get the start and ends of both output polygons ...
|
||||
ring_ptr<T> outRec1 = (*b1)->ring;
|
||||
ring_ptr<T> outRec2 = (*b2)->ring;
|
||||
ring_ptr<T> outRec1 = b1.ring;
|
||||
ring_ptr<T> outRec2 = b2.ring;
|
||||
|
||||
ring_ptr<T> keep_ring;
|
||||
bound_ptr<T> keep_bound;
|
||||
ring_ptr<T> remove_ring;
|
||||
bound_ptr<T> remove_bound;
|
||||
if (ring1_right_of_ring2(outRec1, outRec2)) {
|
||||
if (ring1_child_below_ring2(outRec1, outRec2)) {
|
||||
keep_ring = outRec2;
|
||||
keep_bound = *b2;
|
||||
keep_bound = &b2;
|
||||
remove_ring = outRec1;
|
||||
remove_bound = *b1;
|
||||
} else if (ring1_right_of_ring2(outRec2, outRec1)) {
|
||||
remove_bound = &b1;
|
||||
} else if (ring1_child_below_ring2(outRec2, outRec1)) {
|
||||
keep_ring = outRec1;
|
||||
keep_bound = *b1;
|
||||
keep_bound = &b1;
|
||||
remove_ring = outRec2;
|
||||
remove_bound = *b2;
|
||||
remove_bound = &b2;
|
||||
} else if (outRec1 == get_lower_most_ring(outRec1, outRec2)) {
|
||||
keep_ring = outRec1;
|
||||
keep_bound = *b1;
|
||||
keep_bound = &b1;
|
||||
remove_ring = outRec2;
|
||||
remove_bound = *b2;
|
||||
remove_bound = &b2;
|
||||
} else {
|
||||
keep_ring = outRec2;
|
||||
keep_bound = *b2;
|
||||
keep_bound = &b2;
|
||||
remove_ring = outRec1;
|
||||
remove_bound = *b1;
|
||||
remove_bound = &b1;
|
||||
}
|
||||
|
||||
// get the start and ends of both output polygons and
|
||||
@ -672,6 +594,9 @@ void append_ring(active_bound_list_itr<T>& b1,
|
||||
remove_bound->ring = nullptr;
|
||||
|
||||
for (auto& b : active_bounds) {
|
||||
if (b == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (b->ring == remove_ring) {
|
||||
b->ring = keep_ring;
|
||||
b->side = keep_bound->side;
|
||||
@ -681,21 +606,18 @@ void append_ring(active_bound_list_itr<T>& b1,
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void add_local_maximum_point(active_bound_list_itr<T>& b1,
|
||||
active_bound_list_itr<T>& b2,
|
||||
void add_local_maximum_point(bound<T>& b1,
|
||||
bound<T>& b2,
|
||||
mapbox::geometry::point<T> const& pt,
|
||||
ring_manager<T>& rings,
|
||||
active_bound_list<T>& active_bounds) {
|
||||
insert_hot_pixels_in_path(*(*b2), pt, rings, false);
|
||||
insert_hot_pixels_in_path(b2, pt, rings, false);
|
||||
add_point(b1, active_bounds, pt, rings);
|
||||
if ((*b2)->winding_delta == 0) {
|
||||
add_point(b2, active_bounds, pt, rings);
|
||||
}
|
||||
if ((*b1)->ring == (*b2)->ring) {
|
||||
(*b1)->ring = nullptr;
|
||||
(*b2)->ring = nullptr;
|
||||
if (b1.ring == b2.ring) {
|
||||
b1.ring = nullptr;
|
||||
b2.ring = nullptr;
|
||||
// I am not certain that order is important here?
|
||||
} else if ((*b1)->ring->ring_index < (*b2)->ring->ring_index) {
|
||||
} else if (b1.ring->ring_index < b2.ring->ring_index) {
|
||||
append_ring(b1, b2, active_bounds, rings);
|
||||
} else {
|
||||
append_ring(b2, b1, active_bounds, rings);
|
||||
@ -715,8 +637,7 @@ point_in_polygon_result point_in_polygon(point<T> const& pt, point_ptr<T> op) {
|
||||
point_ptr<T> startOp = op;
|
||||
do {
|
||||
if (op->next->y == pt.y) {
|
||||
if ((op->next->x == pt.x) ||
|
||||
(op->y == pt.y && ((op->next->x > pt.x) == (op->x < pt.x)))) {
|
||||
if ((op->next->x == pt.x) || (op->y == pt.y && ((op->next->x > pt.x) == (op->x < pt.x)))) {
|
||||
return point_on_polygon;
|
||||
}
|
||||
}
|
||||
@ -731,10 +652,8 @@ point_in_polygon_result point_in_polygon(point<T> const& pt, point_ptr<T> op) {
|
||||
result = point_outside_polygon;
|
||||
}
|
||||
} else {
|
||||
double d =
|
||||
static_cast<double>(op->x - pt.x) *
|
||||
static_cast<double>(op->next->y - pt.y) -
|
||||
static_cast<double>(op->next->x - pt.x) * static_cast<double>(op->y - pt.y);
|
||||
double d = static_cast<double>(op->x - pt.x) * static_cast<double>(op->next->y - pt.y) -
|
||||
static_cast<double>(op->next->x - pt.x) * static_cast<double>(op->y - pt.y);
|
||||
if (value_is_zero(d)) {
|
||||
return point_on_polygon;
|
||||
}
|
||||
@ -750,10 +669,8 @@ point_in_polygon_result point_in_polygon(point<T> const& pt, point_ptr<T> op) {
|
||||
}
|
||||
} else {
|
||||
if (op->next->x > pt.x) {
|
||||
double d =
|
||||
static_cast<double>(op->x - pt.x) *
|
||||
static_cast<double>(op->next->y - pt.y) -
|
||||
static_cast<double>(op->next->x - pt.x) * static_cast<double>(op->y - pt.y);
|
||||
double d = static_cast<double>(op->x - pt.x) * static_cast<double>(op->next->y - pt.y) -
|
||||
static_cast<double>(op->next->x - pt.x) * static_cast<double>(op->y - pt.y);
|
||||
if (value_is_zero(d)) {
|
||||
return point_on_polygon;
|
||||
}
|
||||
@ -775,8 +692,7 @@ point_in_polygon_result point_in_polygon(point<T> const& pt, point_ptr<T> op) {
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
point_in_polygon_result point_in_polygon(mapbox::geometry::point<double> const& pt,
|
||||
point_ptr<T> op) {
|
||||
point_in_polygon_result point_in_polygon(mapbox::geometry::point<double> const& pt, point_ptr<T> op) {
|
||||
// returns 0 if false, +1 if true, -1 if pt ON polygon boundary
|
||||
point_in_polygon_result result = point_outside_polygon;
|
||||
point_ptr<T> startOp = op;
|
||||
@ -802,12 +718,11 @@ point_in_polygon_result point_in_polygon(mapbox::geometry::point<double> const&
|
||||
result = point_outside_polygon;
|
||||
}
|
||||
} else {
|
||||
double d =
|
||||
(op_x - pt.x) * (op_next_y - pt.y) - (op_next_x - pt.x) * (op_y - pt.y);
|
||||
double d = (op_x - pt.x) * (op_next_y - pt.y) - (op_next_x - pt.x) * (op_y - pt.y);
|
||||
if (value_is_zero(d)) {
|
||||
return point_on_polygon;
|
||||
}
|
||||
if ((d > 0.0) == (op_next_y > op->y)) {
|
||||
if ((d > 0.0) == (op_next_y > op_y)) {
|
||||
// Switch between point outside polygon and point inside
|
||||
// polygon
|
||||
if (result == point_outside_polygon) {
|
||||
@ -819,12 +734,11 @@ point_in_polygon_result point_in_polygon(mapbox::geometry::point<double> const&
|
||||
}
|
||||
} else {
|
||||
if (op_next_x > pt.x) {
|
||||
double d =
|
||||
(op_x - pt.x) * (op_next_y - pt.y) - (op_next_x - pt.x) * (op_y - pt.y);
|
||||
double d = (op_x - pt.x) * (op_next_y - pt.y) - (op_next_x - pt.x) * (op_y - pt.y);
|
||||
if (value_is_zero(d)) {
|
||||
return point_on_polygon;
|
||||
}
|
||||
if ((d > 0.0) == (op_next_y > op->y)) {
|
||||
if ((d > 0.0) == (op_next_y > op_y)) {
|
||||
// Switch between point outside polygon and point inside
|
||||
// polygon
|
||||
if (result == point_outside_polygon) {
|
||||
@ -842,46 +756,69 @@ point_in_polygon_result point_in_polygon(mapbox::geometry::point<double> const&
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
point_in_polygon_result inside_or_outside_special(point_ptr<T> first_pt, point_ptr<T> other_poly) {
|
||||
|
||||
if (value_is_zero(area(first_pt->ring))) {
|
||||
return point_inside_polygon;
|
||||
bool is_convex(point_ptr<T> edge) {
|
||||
point_ptr<T> prev = edge->prev;
|
||||
point_ptr<T> next = edge->next;
|
||||
T v1x = edge->x - prev->x;
|
||||
T v1y = edge->y - prev->y;
|
||||
T v2x = next->x - edge->x;
|
||||
T v2y = next->y - edge->y;
|
||||
T cross = v1x * v2y - v2x * v1y;
|
||||
if (cross < 0 && edge->ring->area() > 0) {
|
||||
return true;
|
||||
} else if (cross > 0 && edge->ring->area() < 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
if (value_is_zero(area(other_poly->ring))) {
|
||||
return point_outside_polygon;
|
||||
}
|
||||
point_ptr<T> pt = first_pt;
|
||||
do {
|
||||
if (*pt == *(pt->prev) || *pt == *(pt->next) || *(pt->next) == *(pt->prev) ||
|
||||
slopes_equal(*(pt->prev), *pt, *(pt->next))) {
|
||||
pt = pt->next;
|
||||
continue;
|
||||
}
|
||||
double dx = ((pt->prev->x - pt->x) / 3.0) + ((pt->next->x - pt->x) / 3.0);
|
||||
double dy = ((pt->prev->y - pt->y) / 3.0) + ((pt->next->y - pt->y) / 3.0);
|
||||
mapbox::geometry::point<double> offset_pt(pt->x + dx, pt->y + dy);
|
||||
point_in_polygon_result res = point_in_polygon(offset_pt, pt);
|
||||
if (res != point_inside_polygon) {
|
||||
offset_pt.x = pt->x - dx;
|
||||
offset_pt.y = pt->y - dy;
|
||||
res = point_in_polygon(offset_pt, pt);
|
||||
if (res != point_inside_polygon) {
|
||||
pt = pt->next;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
res = point_in_polygon(offset_pt, other_poly);
|
||||
if (res == point_on_polygon) {
|
||||
pt = pt->next;
|
||||
continue;
|
||||
}
|
||||
return res;
|
||||
} while (pt != first_pt);
|
||||
return point_inside_polygon;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool poly2_contains_poly1(point_ptr<T> outpt1, point_ptr<T> outpt2) {
|
||||
mapbox::geometry::point<double> centroid_of_points(point_ptr<T> edge) {
|
||||
point_ptr<T> prev = edge->prev;
|
||||
point_ptr<T> next = edge->next;
|
||||
return { static_cast<double>(prev->x + edge->x + next->x) / 3.0,
|
||||
static_cast<double>(prev->y + edge->y + next->y) / 3.0 };
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
point_in_polygon_result inside_or_outside_special(point_ptr<T> first_pt, point_ptr<T> other_poly) {
|
||||
|
||||
// We are going to loop through all the points
|
||||
// of the original triangle. The goal is to find a convex edge
|
||||
// that with its next and previous forms a triangle with its centroid
|
||||
// that is within the first ring. Then we will check the other polygon
|
||||
// to see if it is within this polygon.
|
||||
point_ptr<T> itr = first_pt;
|
||||
do {
|
||||
if (is_convex(itr)) {
|
||||
auto pt = centroid_of_points(itr);
|
||||
if (point_inside_polygon == point_in_polygon(pt, first_pt)) {
|
||||
return point_in_polygon(pt, other_poly);
|
||||
}
|
||||
}
|
||||
itr = itr->next;
|
||||
} while (itr != first_pt);
|
||||
|
||||
throw std::runtime_error("Could not find a point within the polygon to test");
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool box2_contains_box1(mapbox::geometry::box<T> const& box1, mapbox::geometry::box<T> const& box2) {
|
||||
return (box2.max.x >= box1.max.x && box2.max.y >= box1.max.y && box2.min.x <= box1.min.x &&
|
||||
box2.min.y <= box1.min.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool poly2_contains_poly1(ring_ptr<T> ring1, ring_ptr<T> ring2) {
|
||||
if (!box2_contains_box1(ring1->bbox, ring2->bbox)) {
|
||||
return false;
|
||||
}
|
||||
if (std::fabs(ring2->area()) < std::fabs(ring1->area())) {
|
||||
return false;
|
||||
}
|
||||
point_ptr<T> outpt1 = ring1->points->next;
|
||||
point_ptr<T> outpt2 = ring2->points->next;
|
||||
point_ptr<T> op = outpt1;
|
||||
do {
|
||||
// nb: PointInPolygon returns 0 if false, +1 if true, -1 if pt on polygon
|
||||
@ -894,22 +831,6 @@ bool poly2_contains_poly1(point_ptr<T> outpt1, point_ptr<T> outpt2) {
|
||||
point_in_polygon_result res = inside_or_outside_special(outpt1, outpt2);
|
||||
return res == point_inside_polygon;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void dispose_out_points(point_ptr<T>& pp) {
|
||||
if (pp == nullptr) {
|
||||
return;
|
||||
}
|
||||
pp->prev->next = nullptr;
|
||||
while (pp) {
|
||||
point_ptr<T> tmpPp = pp;
|
||||
pp = pp->next;
|
||||
tmpPp->next = tmpPp;
|
||||
tmpPp->prev = tmpPp;
|
||||
tmpPp->ring = nullptr;
|
||||
// delete tmpPp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -1,37 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include <queue>
|
||||
|
||||
#include <mapbox/geometry/wagyu/config.hpp>
|
||||
#include <mapbox/geometry/wagyu/local_minimum.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
template <typename T>
|
||||
using scanbeam_list = std::priority_queue<T>;
|
||||
using scanbeam_list = std::vector<T>;
|
||||
|
||||
template <typename T>
|
||||
void insert_sorted_scanbeam(scanbeam_list<T>& scanbeam, T& t) {
|
||||
typename scanbeam_list<T>::iterator i = std::lower_bound(scanbeam.begin(), scanbeam.end(), t);
|
||||
if (i == scanbeam.end() || t < *i) {
|
||||
scanbeam.insert(i, t);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool pop_from_scanbeam(T& Y, scanbeam_list<T>& scanbeam) {
|
||||
if (scanbeam.empty()) {
|
||||
return false;
|
||||
}
|
||||
Y = scanbeam.top();
|
||||
scanbeam.pop();
|
||||
while (!scanbeam.empty() && Y == scanbeam.top()) {
|
||||
scanbeam.pop();
|
||||
} // Pop duplicates.
|
||||
|
||||
Y = scanbeam.back();
|
||||
scanbeam.pop_back();
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void setup_scanbeam(local_minimum_list<T>& minima_list, scanbeam_list<T>& scanbeam) {
|
||||
|
||||
scanbeam.reserve(minima_list.size());
|
||||
for (auto lm = minima_list.begin(); lm != minima_list.end(); ++lm) {
|
||||
scanbeam.push(lm->y);
|
||||
scanbeam.push_back(lm->y);
|
||||
}
|
||||
std::sort(scanbeam.begin(), scanbeam.end());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -1,9 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <mapbox/geometry/wagyu/active_bound_list.hpp>
|
||||
#include <mapbox/geometry/wagyu/edge.hpp>
|
||||
#include <mapbox/geometry/wagyu/bound.hpp>
|
||||
#include <mapbox/geometry/wagyu/bubble_sort.hpp>
|
||||
#include <mapbox/geometry/wagyu/config.hpp>
|
||||
#include <mapbox/geometry/wagyu/edge.hpp>
|
||||
#include <mapbox/geometry/wagyu/intersect.hpp>
|
||||
#include <mapbox/geometry/wagyu/intersect_util.hpp>
|
||||
#include <mapbox/geometry/wagyu/ring.hpp>
|
||||
@ -15,124 +16,143 @@ namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
template <typename T>
|
||||
void process_hot_pixel_intersections(T top_y,
|
||||
active_bound_list<T>& active_bounds,
|
||||
ring_manager<T>& rings) {
|
||||
struct hp_intersection_swap {
|
||||
|
||||
ring_manager<T>& manager;
|
||||
|
||||
hp_intersection_swap(ring_manager<T>& m) : manager(m) {
|
||||
}
|
||||
|
||||
void operator()(bound_ptr<T> const& b1, bound_ptr<T> const& b2) {
|
||||
mapbox::geometry::point<double> pt;
|
||||
if (!get_edge_intersection<T, double>(*(b1->current_edge), *(b2->current_edge), pt)) {
|
||||
// LCOV_EXCL_START
|
||||
throw std::runtime_error("Trying to find intersection of lines that do not intersect");
|
||||
// LCOV_EXCL_END
|
||||
}
|
||||
add_to_hot_pixels(round_point<T>(pt), manager);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
void process_hot_pixel_intersections(T top_y, active_bound_list<T>& active_bounds, ring_manager<T>& manager) {
|
||||
if (active_bounds.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_current_x(active_bounds, top_y);
|
||||
// bubblesort ...
|
||||
bool isModified;
|
||||
do {
|
||||
isModified = false;
|
||||
auto bnd = active_bounds.begin();
|
||||
auto bnd_next = std::next(bnd);
|
||||
while (bnd_next != active_bounds.end()) {
|
||||
if ((*bnd)->current_x > (*bnd_next)->current_x && !slopes_equal(*(*bnd)->current_edge, *(*bnd_next)->current_edge)) {
|
||||
mapbox::geometry::point<double> pt;
|
||||
if (!get_edge_intersection<T, double>(*((*bnd)->current_edge),
|
||||
*((*bnd_next)->current_edge), pt)) {
|
||||
throw std::runtime_error("Edges do not intersect!");
|
||||
bubble_sort(active_bounds.begin(), active_bounds.end(), intersection_compare<T>(),
|
||||
hp_intersection_swap<T>(manager));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool horizontals_at_top_scanbeam(T top_y,
|
||||
active_bound_list_itr<T>& bnd_curr,
|
||||
active_bound_list<T>& active_bounds,
|
||||
ring_manager<T>& manager) {
|
||||
bool shifted = false;
|
||||
auto& current_edge = (*bnd_curr)->current_edge;
|
||||
(*bnd_curr)->current_x = static_cast<double>(current_edge->top.x);
|
||||
if (current_edge->bot.x < current_edge->top.x) {
|
||||
// left to right
|
||||
auto bnd_next = std::next(bnd_curr);
|
||||
while (bnd_next != active_bounds.end() &&
|
||||
(*bnd_next == nullptr || (*bnd_next)->current_x < (*bnd_curr)->current_x)) {
|
||||
if (*bnd_next != nullptr && (*bnd_next)->current_edge->top.y != top_y &&
|
||||
(*bnd_next)->current_edge->bot.y != top_y) {
|
||||
mapbox::geometry::point<T> pt(wround<T>((*bnd_next)->current_x), top_y);
|
||||
add_to_hot_pixels(pt, manager);
|
||||
}
|
||||
std::iter_swap(bnd_curr, bnd_next);
|
||||
++bnd_curr;
|
||||
++bnd_next;
|
||||
shifted = true;
|
||||
}
|
||||
} else {
|
||||
// right to left
|
||||
if (bnd_curr != active_bounds.begin()) {
|
||||
auto bnd_prev = std::prev(bnd_curr);
|
||||
while (bnd_curr != active_bounds.begin() &&
|
||||
(*bnd_prev == nullptr || (*bnd_prev)->current_x > (*bnd_curr)->current_x)) {
|
||||
if (*bnd_prev != nullptr && (*bnd_prev)->current_edge->top.y != top_y &&
|
||||
(*bnd_prev)->current_edge->bot.y != top_y) {
|
||||
mapbox::geometry::point<T> pt(wround<T>((*bnd_prev)->current_x), top_y);
|
||||
add_to_hot_pixels(pt, manager);
|
||||
}
|
||||
std::iter_swap(bnd_curr, bnd_prev);
|
||||
--bnd_curr;
|
||||
if (bnd_curr != active_bounds.begin()) {
|
||||
--bnd_prev;
|
||||
}
|
||||
add_to_hot_pixels(round_point<T>(pt), rings);
|
||||
swap_positions_in_ABL(bnd, bnd_next, active_bounds);
|
||||
bnd_next = std::next(bnd);
|
||||
isModified = true;
|
||||
} else {
|
||||
bnd = bnd_next;
|
||||
++bnd_next;
|
||||
}
|
||||
}
|
||||
} while (isModified);
|
||||
}
|
||||
return shifted;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void process_hot_pixel_edges_at_top_of_scanbeam(T top_y,
|
||||
scanbeam_list<T>& scanbeam,
|
||||
active_bound_list<T>& active_bounds,
|
||||
ring_manager<T>& rings) {
|
||||
ring_manager<T>& manager) {
|
||||
for (auto bnd = active_bounds.begin(); bnd != active_bounds.end();) {
|
||||
auto bnd_2 = std::next(bnd);
|
||||
while ((*bnd)->current_edge != (*bnd)->edges.end() && (*bnd)->current_edge->top.y == top_y) {
|
||||
add_to_hot_pixels((*bnd)->current_edge->top, rings);
|
||||
if (current_edge_is_horizontal<T>(bnd)) {
|
||||
(*bnd)->current_x = static_cast<double>((*bnd)->current_edge->top.x);
|
||||
if ((*bnd)->current_edge->bot.x < (*bnd)->current_edge->top.x) {
|
||||
// left to right
|
||||
auto bnd_next = std::next(bnd);
|
||||
while (bnd_next != active_bounds.end() && (*bnd_next)->current_x < (*bnd)->current_x) {
|
||||
if (std::llround((*bnd_next)->current_edge->top.y) != top_y && std::llround((*bnd_next)->current_edge->bot.y) != top_y) {
|
||||
mapbox::geometry::point<T> pt(std::llround((*bnd_next)->current_x), top_y);
|
||||
add_to_hot_pixels(pt, rings);
|
||||
}
|
||||
swap_positions_in_ABL(bnd, bnd_next, active_bounds);
|
||||
bnd_next = std::next(bnd);
|
||||
}
|
||||
} else {
|
||||
// right to left
|
||||
if (bnd != active_bounds.begin()) {
|
||||
auto bnd_prev = std::prev(bnd);
|
||||
while (bnd != active_bounds.begin() && (*bnd_prev)->current_x > (*bnd)->current_x) {
|
||||
if (std::llround((*bnd_prev)->current_edge->top.y) != top_y && std::llround((*bnd_prev)->current_edge->bot.y) != top_y) {
|
||||
mapbox::geometry::point<T> pt(std::llround((*bnd_prev)->current_x), top_y);
|
||||
add_to_hot_pixels(pt, rings);
|
||||
}
|
||||
swap_positions_in_ABL(bnd, bnd_prev, active_bounds);
|
||||
bnd_prev = std::prev(bnd);
|
||||
}
|
||||
}
|
||||
if (*bnd == nullptr) {
|
||||
++bnd;
|
||||
continue;
|
||||
}
|
||||
bound<T>& current_bound = *(*bnd);
|
||||
auto bnd_curr = bnd;
|
||||
bool shifted = false;
|
||||
auto& current_edge = current_bound.current_edge;
|
||||
while (current_edge != current_bound.edges.end() && current_edge->top.y == top_y) {
|
||||
add_to_hot_pixels(current_edge->top, manager);
|
||||
if (is_horizontal(*current_edge)) {
|
||||
if (horizontals_at_top_scanbeam(top_y, bnd_curr, active_bounds, manager)) {
|
||||
shifted = true;
|
||||
}
|
||||
}
|
||||
next_edge_in_bound(bnd, scanbeam);
|
||||
next_edge_in_bound(current_bound, scanbeam);
|
||||
}
|
||||
if ((*bnd)->current_edge == (*bnd)->edges.end()) {
|
||||
active_bounds.erase(bnd);
|
||||
if (current_edge == current_bound.edges.end()) {
|
||||
*bnd_curr = nullptr;
|
||||
}
|
||||
if (!shifted) {
|
||||
++bnd;
|
||||
}
|
||||
bnd = bnd_2;
|
||||
}
|
||||
active_bounds.erase(std::remove(active_bounds.begin(), active_bounds.end(), nullptr), active_bounds.end());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void insert_local_minima_into_ABL_hot_pixel(T top_y,
|
||||
local_minimum_ptr_list<T> & minima_sorted,
|
||||
local_minimum_ptr_list_itr<T> & lm,
|
||||
local_minimum_ptr_list<T>& minima_sorted,
|
||||
local_minimum_ptr_list_itr<T>& lm,
|
||||
active_bound_list<T>& active_bounds,
|
||||
ring_manager<T>& rings,
|
||||
ring_manager<T>& manager,
|
||||
scanbeam_list<T>& scanbeam) {
|
||||
while (lm != minima_sorted.end() && (*lm)->y == top_y) {
|
||||
if ((*lm)->left_bound.edges.empty() || (*lm)->right_bound.edges.empty()) {
|
||||
++lm;
|
||||
continue;
|
||||
}
|
||||
|
||||
add_to_hot_pixels((*lm)->left_bound.edges.front().bot, rings);
|
||||
add_to_hot_pixels((*lm)->left_bound.edges.front().bot, manager);
|
||||
auto& left_bound = (*lm)->left_bound;
|
||||
left_bound.current_edge = left_bound.edges.begin();
|
||||
left_bound.current_x = static_cast<double>(left_bound.current_edge->bot.x);
|
||||
auto lb_abl_itr = insert_bound_into_ABL(left_bound, active_bounds);
|
||||
if (!current_edge_is_horizontal<T>(lb_abl_itr)) {
|
||||
scanbeam.push((*lb_abl_itr)->current_edge->top.y);
|
||||
}
|
||||
auto& right_bound = (*lm)->right_bound;
|
||||
left_bound.current_edge = left_bound.edges.begin();
|
||||
left_bound.next_edge = std::next(left_bound.current_edge);
|
||||
left_bound.current_x = static_cast<double>(left_bound.current_edge->bot.x);
|
||||
right_bound.current_edge = right_bound.edges.begin();
|
||||
right_bound.next_edge = std::next(right_bound.current_edge);
|
||||
right_bound.current_x = static_cast<double>(right_bound.current_edge->bot.x);
|
||||
auto rb_abl_itr = insert_bound_into_ABL(right_bound, lb_abl_itr, active_bounds);
|
||||
auto lb_abl_itr = insert_bound_into_ABL(left_bound, right_bound, active_bounds);
|
||||
if (!current_edge_is_horizontal<T>(lb_abl_itr)) {
|
||||
insert_sorted_scanbeam(scanbeam, (*lb_abl_itr)->current_edge->top.y);
|
||||
}
|
||||
auto rb_abl_itr = std::next(lb_abl_itr);
|
||||
if (!current_edge_is_horizontal<T>(rb_abl_itr)) {
|
||||
scanbeam.push((*rb_abl_itr)->current_edge->top.y);
|
||||
insert_sorted_scanbeam(scanbeam, (*rb_abl_itr)->current_edge->top.y);
|
||||
}
|
||||
++lm;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void build_hot_pixels(local_minimum_list<T>& minima_list,
|
||||
ring_manager<T>& rings) {
|
||||
if (minima_list.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
void build_hot_pixels(local_minimum_list<T>& minima_list, ring_manager<T>& manager) {
|
||||
active_bound_list<T> active_bounds;
|
||||
scanbeam_list<T> scanbeam;
|
||||
T scanline_y = std::numeric_limits<T>::max();
|
||||
@ -149,26 +169,23 @@ void build_hot_pixels(local_minimum_list<T>& minima_list,
|
||||
|
||||
// Estimate size for reserving hot pixels
|
||||
std::size_t reserve = 0;
|
||||
for (auto & lm : minima_list) {
|
||||
for (auto& lm : minima_list) {
|
||||
reserve += lm.left_bound.edges.size() + 2;
|
||||
reserve += lm.right_bound.edges.size() + 2;
|
||||
}
|
||||
rings.hot_pixels.reserve(reserve);
|
||||
manager.hot_pixels.reserve(reserve);
|
||||
|
||||
while (pop_from_scanbeam(scanline_y, scanbeam) || current_lm != minima_sorted.end()) {
|
||||
|
||||
process_hot_pixel_intersections(scanline_y, active_bounds, rings);
|
||||
process_hot_pixel_intersections(scanline_y, active_bounds, manager);
|
||||
|
||||
insert_local_minima_into_ABL_hot_pixel(scanline_y, minima_sorted, current_lm, active_bounds,
|
||||
rings, scanbeam);
|
||||
|
||||
process_hot_pixel_edges_at_top_of_scanbeam(scanline_y, scanbeam, active_bounds, rings);
|
||||
|
||||
insert_local_minima_into_ABL_hot_pixel(scanline_y, minima_sorted, current_lm, active_bounds, manager, scanbeam);
|
||||
|
||||
process_hot_pixel_edges_at_top_of_scanbeam(scanline_y, scanbeam, active_bounds, manager);
|
||||
}
|
||||
preallocate_point_memory(rings, rings.hot_pixels.size());
|
||||
sort_hot_pixels(rings);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
preallocate_point_memory(manager, manager.hot_pixels.size());
|
||||
sort_hot_pixels(manager);
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -4,6 +4,7 @@
|
||||
|
||||
#include <mapbox/geometry/point.hpp>
|
||||
#include <mapbox/geometry/polygon.hpp>
|
||||
#include <mapbox/geometry/wagyu/almost_equal.hpp>
|
||||
#include <mapbox/geometry/wagyu/point.hpp>
|
||||
|
||||
namespace mapbox {
|
||||
@ -30,45 +31,48 @@ double area(mapbox::geometry::linear_ring<T> const& poly) {
|
||||
return -a * 0.5;
|
||||
}
|
||||
|
||||
inline bool value_is_zero(double val) {
|
||||
return std::fabs(val) < std::numeric_limits<double>::epsilon();
|
||||
}
|
||||
|
||||
inline bool values_are_equal(double x, double y) {
|
||||
return value_is_zero(x - y);
|
||||
return util::FloatingPoint<double>(x).AlmostEquals(util::FloatingPoint<double>(y));
|
||||
}
|
||||
|
||||
inline bool values_near_equal(double x, double y) {
|
||||
return std::fabs(x - y) < (5.0 * std::numeric_limits<double>::epsilon());
|
||||
inline bool value_is_zero(double val) {
|
||||
return values_are_equal(val, static_cast<double>(0.0));
|
||||
}
|
||||
|
||||
inline bool greater_than_or_equal(double x, double y) {
|
||||
return x > y || values_are_equal(x, y);
|
||||
}
|
||||
|
||||
inline bool less_than_or_equal(double x, double y) {
|
||||
return x < y || values_are_equal(x, y);
|
||||
inline bool greater_than(double x, double y) {
|
||||
return (!values_are_equal(x, y) && x > y);
|
||||
}
|
||||
|
||||
inline bool less_than(double x, double y) {
|
||||
return (!values_are_equal(x, y) && x < y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool slopes_equal(mapbox::geometry::point<T> const& pt1,
|
||||
mapbox::geometry::point<T> const& pt2,
|
||||
mapbox::geometry::point<T> const& pt3) {
|
||||
return (pt1.y - pt2.y) * (pt2.x - pt3.x) == (pt1.x - pt2.x) * (pt2.y - pt3.y);
|
||||
return static_cast<std::int64_t>(pt1.y - pt2.y) * static_cast<std::int64_t>(pt2.x - pt3.x) ==
|
||||
static_cast<std::int64_t>(pt1.x - pt2.x) * static_cast<std::int64_t>(pt2.y - pt3.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool slopes_equal(mapbox::geometry::wagyu::point<T> const& pt1,
|
||||
mapbox::geometry::wagyu::point<T> const& pt2,
|
||||
mapbox::geometry::point<T> const& pt3) {
|
||||
return (pt1.y - pt2.y) * (pt2.x - pt3.x) == (pt1.x - pt2.x) * (pt2.y - pt3.y);
|
||||
return static_cast<std::int64_t>(pt1.y - pt2.y) * static_cast<std::int64_t>(pt2.x - pt3.x) ==
|
||||
static_cast<std::int64_t>(pt1.x - pt2.x) * static_cast<std::int64_t>(pt2.y - pt3.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool slopes_equal(mapbox::geometry::wagyu::point<T> const& pt1,
|
||||
mapbox::geometry::wagyu::point<T> const& pt2,
|
||||
mapbox::geometry::wagyu::point<T> const& pt3) {
|
||||
return (pt1.y - pt2.y) * (pt2.x - pt3.x) == (pt1.x - pt2.x) * (pt2.y - pt3.y);
|
||||
return static_cast<std::int64_t>(pt1.y - pt2.y) * static_cast<std::int64_t>(pt2.x - pt3.x) ==
|
||||
static_cast<std::int64_t>(pt1.x - pt2.x) * static_cast<std::int64_t>(pt2.y - pt3.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@ -76,8 +80,19 @@ bool slopes_equal(mapbox::geometry::point<T> const& pt1,
|
||||
mapbox::geometry::point<T> const& pt2,
|
||||
mapbox::geometry::point<T> const& pt3,
|
||||
mapbox::geometry::point<T> const& pt4) {
|
||||
return (pt1.y - pt2.y) * (pt3.x - pt4.x) == (pt1.x - pt2.x) * (pt3.y - pt4.y);
|
||||
}
|
||||
return static_cast<std::int64_t>(pt1.y - pt2.y) * static_cast<std::int64_t>(pt3.x - pt4.x) ==
|
||||
static_cast<std::int64_t>(pt1.x - pt2.x) * static_cast<std::int64_t>(pt3.y - pt4.y);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline T wround(double value) {
|
||||
return static_cast<T>(::llround(value));
|
||||
}
|
||||
|
||||
template <>
|
||||
inline std::int64_t wround<std::int64_t>(double value) {
|
||||
return ::llround(value);
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -5,7 +5,6 @@
|
||||
|
||||
#include <mapbox/geometry/wagyu/active_bound_list.hpp>
|
||||
#include <mapbox/geometry/wagyu/config.hpp>
|
||||
#include <mapbox/geometry/wagyu/exceptions.hpp>
|
||||
#include <mapbox/geometry/wagyu/intersect_util.hpp>
|
||||
#include <mapbox/geometry/wagyu/local_minimum.hpp>
|
||||
#include <mapbox/geometry/wagyu/local_minimum_util.hpp>
|
||||
@ -20,16 +19,11 @@ namespace geometry {
|
||||
namespace wagyu {
|
||||
|
||||
template <typename T>
|
||||
bool execute_vatti(local_minimum_list<T>& minima_list,
|
||||
ring_manager<T>& rings,
|
||||
void execute_vatti(local_minimum_list<T>& minima_list,
|
||||
ring_manager<T>& manager,
|
||||
clip_type cliptype,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
|
||||
if (minima_list.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
active_bound_list<T> active_bounds;
|
||||
scanbeam_list<T> scanbeam;
|
||||
T scanline_y = std::numeric_limits<T>::max();
|
||||
@ -44,34 +38,27 @@ bool execute_vatti(local_minimum_list<T>& minima_list,
|
||||
// std::clog << output_all_edges(minima_sorted) << std::endl;
|
||||
|
||||
setup_scanbeam(minima_list, scanbeam);
|
||||
rings.current_hp_itr = rings.hot_pixels.begin();
|
||||
manager.current_hp_itr = manager.hot_pixels.begin();
|
||||
|
||||
while (pop_from_scanbeam(scanline_y, scanbeam) || current_lm != minima_sorted.end()) {
|
||||
|
||||
process_intersections(scanline_y, active_bounds, cliptype,
|
||||
subject_fill_type, clip_fill_type, rings);
|
||||
process_intersections(scanline_y, active_bounds, cliptype, subject_fill_type, clip_fill_type, manager);
|
||||
|
||||
update_current_hp_itr(scanline_y, rings);
|
||||
update_current_hp_itr(scanline_y, manager);
|
||||
|
||||
// First we process bounds that has already been added to the active bound list --
|
||||
// if the active bound list is empty local minima that are at this scanline_y and
|
||||
// have a horizontal edge at the local minima will be processed
|
||||
process_edges_at_top_of_scanbeam(scanline_y, active_bounds, scanbeam, minima_sorted,
|
||||
current_lm, rings, cliptype, subject_fill_type,
|
||||
clip_fill_type);
|
||||
process_edges_at_top_of_scanbeam(scanline_y, active_bounds, scanbeam, minima_sorted, current_lm, manager,
|
||||
cliptype, subject_fill_type, clip_fill_type);
|
||||
|
||||
// Next we will add local minima bounds to the active bounds list that are on the local
|
||||
// minima queue at
|
||||
// this current scanline_y
|
||||
insert_local_minima_into_ABL(scanline_y, minima_sorted, current_lm, active_bounds,
|
||||
rings, scanbeam, cliptype, subject_fill_type,
|
||||
clip_fill_type);
|
||||
|
||||
insert_local_minima_into_ABL(scanline_y, minima_sorted, current_lm, active_bounds, manager, scanbeam, cliptype,
|
||||
subject_fill_type, clip_fill_type);
|
||||
}
|
||||
// std::clog << rings.rings << std::endl;
|
||||
// std::clog << output_as_polygon(rings.all_rings[0]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
@ -7,14 +7,21 @@
|
||||
#include <mapbox/geometry/multi_polygon.hpp>
|
||||
#include <mapbox/geometry/polygon.hpp>
|
||||
|
||||
#include <mapbox/geometry/wagyu/build_result.hpp>
|
||||
#include <mapbox/geometry/wagyu/build_local_minima_list.hpp>
|
||||
#include <mapbox/geometry/wagyu/build_result.hpp>
|
||||
#include <mapbox/geometry/wagyu/config.hpp>
|
||||
#include <mapbox/geometry/wagyu/interrupt.hpp>
|
||||
#include <mapbox/geometry/wagyu/local_minimum.hpp>
|
||||
#include <mapbox/geometry/wagyu/snap_rounding.hpp>
|
||||
#include <mapbox/geometry/wagyu/topology_correction.hpp>
|
||||
#include <mapbox/geometry/wagyu/vatti.hpp>
|
||||
|
||||
#define WAGYU_MAJOR_VERSION 0
|
||||
#define WAGYU_MINOR_VERSION 5
|
||||
#define WAGYU_PATCH_VERSION 0
|
||||
|
||||
#define WAGYU_VERSION (WAGYU_MAJOR_VERSION * 100000) + (WAGYU_MINOR_VERSION * 100) + (WAGYU_PATCH_VERSION)
|
||||
|
||||
namespace mapbox {
|
||||
namespace geometry {
|
||||
namespace wagyu {
|
||||
@ -22,38 +29,27 @@ namespace wagyu {
|
||||
template <typename T>
|
||||
class wagyu {
|
||||
private:
|
||||
using value_type = T;
|
||||
|
||||
local_minimum_list<value_type> minima_list;
|
||||
bool has_open_paths;
|
||||
local_minimum_list<T> minima_list;
|
||||
bool reverse_output;
|
||||
|
||||
wagyu( wagyu const& ) = delete;
|
||||
wagyu& operator=(wagyu const& ) = delete;
|
||||
wagyu(wagyu const&) = delete;
|
||||
wagyu& operator=(wagyu const&) = delete;
|
||||
|
||||
public:
|
||||
wagyu() : minima_list(), has_open_paths(false), reverse_output(false) {
|
||||
wagyu() : minima_list(), reverse_output(false) {
|
||||
}
|
||||
|
||||
~wagyu() {
|
||||
clear();
|
||||
}
|
||||
|
||||
bool add_line(mapbox::geometry::line_string<value_type> const& pg) {
|
||||
bool success = add_line_string(pg, minima_list);
|
||||
if (success) {
|
||||
has_open_paths = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
bool add_ring(mapbox::geometry::linear_ring<value_type> const& pg,
|
||||
polygon_type p_type = polygon_type_subject) {
|
||||
template <typename T2>
|
||||
bool add_ring(mapbox::geometry::linear_ring<T2> const& pg, polygon_type p_type = polygon_type_subject) {
|
||||
return add_linear_ring(pg, minima_list, p_type);
|
||||
}
|
||||
|
||||
bool add_polygon(mapbox::geometry::polygon<value_type> const& ppg,
|
||||
polygon_type p_type = polygon_type_subject) {
|
||||
template <typename T2>
|
||||
bool add_polygon(mapbox::geometry::polygon<T2> const& ppg, polygon_type p_type = polygon_type_subject) {
|
||||
bool result = false;
|
||||
for (auto const& r : ppg) {
|
||||
if (add_ring(r, p_type)) {
|
||||
@ -69,14 +65,13 @@ public:
|
||||
|
||||
void clear() {
|
||||
minima_list.clear();
|
||||
has_open_paths = false;
|
||||
}
|
||||
|
||||
mapbox::geometry::box<value_type> get_bounds() {
|
||||
mapbox::geometry::point<value_type> min = { 0, 0 };
|
||||
mapbox::geometry::point<value_type> max = { 0, 0 };
|
||||
mapbox::geometry::box<T> get_bounds() {
|
||||
mapbox::geometry::point<T> min = { 0, 0 };
|
||||
mapbox::geometry::point<T> max = { 0, 0 };
|
||||
if (minima_list.empty()) {
|
||||
return mapbox::geometry::box<value_type>(min, max);
|
||||
return mapbox::geometry::box<T>(min, max);
|
||||
}
|
||||
bool first_set = false;
|
||||
for (auto const& lm : minima_list) {
|
||||
@ -113,29 +108,38 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
return mapbox::geometry::box<value_type>(min, max);
|
||||
return mapbox::geometry::box<T>(min, max);
|
||||
}
|
||||
|
||||
template <typename T2>
|
||||
bool execute(clip_type cliptype,
|
||||
mapbox::geometry::multi_polygon<value_type>& solution,
|
||||
mapbox::geometry::multi_polygon<T2>& solution,
|
||||
fill_type subject_fill_type,
|
||||
fill_type clip_fill_type) {
|
||||
|
||||
ring_manager<T> rings;
|
||||
|
||||
build_hot_pixels(minima_list, rings);
|
||||
|
||||
if (!execute_vatti(minima_list, rings, cliptype, subject_fill_type, clip_fill_type)) {
|
||||
if (minima_list.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
do_simple_polygons(rings);
|
||||
|
||||
build_result(solution, rings, reverse_output);
|
||||
ring_manager<T> manager;
|
||||
|
||||
interrupt_check(); // Check for interruptions
|
||||
|
||||
build_hot_pixels(minima_list, manager);
|
||||
|
||||
interrupt_check(); // Check for interruptions
|
||||
|
||||
execute_vatti(minima_list, manager, cliptype, subject_fill_type, clip_fill_type);
|
||||
|
||||
interrupt_check(); // Check for interruptions
|
||||
|
||||
correct_topology(manager);
|
||||
|
||||
build_result(solution, manager, reverse_output);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace wagyu
|
||||
} // namespace geometry
|
||||
} // namespace mapbox
|
||||
|
567
mbtiles.cpp
567
mbtiles.cpp
@ -11,10 +11,17 @@
|
||||
#include <string>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include "main.hpp"
|
||||
#include "pool.hpp"
|
||||
#include <sys/stat.h>
|
||||
#include "mvt.hpp"
|
||||
#include "mbtiles.hpp"
|
||||
#include "geometry.hpp"
|
||||
#include "text.hpp"
|
||||
#include "milo/dtoa_milo.h"
|
||||
#include "write_json.hpp"
|
||||
#include "version.hpp"
|
||||
|
||||
size_t max_tilestats_attributes = 1000;
|
||||
size_t max_tilestats_sample_values = 1000;
|
||||
size_t max_tilestats_values = 100;
|
||||
|
||||
sqlite3 *mbtiles_open(char *dbname, char **argv, int forcetable) {
|
||||
sqlite3 *outdb;
|
||||
@ -38,7 +45,8 @@ sqlite3 *mbtiles_open(char *dbname, char **argv, int forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (sqlite3_exec(outdb, "CREATE TABLE metadata (name text, value text);", NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "%s: create metadata table: %s\n", argv[0], err);
|
||||
fprintf(stderr, "%s: Tileset \"%s\" already exists. You can use --force if you want to delete the old tileset.\n", argv[0], dbname);
|
||||
fprintf(stderr, "%s: %s\n", argv[0], err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
@ -86,43 +94,6 @@ void mbtiles_write_tile(sqlite3 *outdb, int z, int tx, int ty, const char *data,
|
||||
}
|
||||
}
|
||||
|
||||
static void quote(std::string *buf, const char *s) {
|
||||
char tmp[strlen(s) * 8 + 1];
|
||||
char *out = tmp;
|
||||
|
||||
for (; *s != '\0'; s++) {
|
||||
unsigned char ch = (unsigned char) *s;
|
||||
|
||||
if (ch == '\\' || ch == '\"') {
|
||||
*out++ = '\\';
|
||||
*out++ = ch;
|
||||
} else if (ch < ' ') {
|
||||
sprintf(out, "\\u%04x", ch);
|
||||
out = out + strlen(out);
|
||||
} else {
|
||||
*out++ = ch;
|
||||
}
|
||||
}
|
||||
|
||||
*out = '\0';
|
||||
buf->append(tmp, strlen(tmp));
|
||||
}
|
||||
|
||||
void aprintf(std::string *buf, const char *format, ...) {
|
||||
va_list ap;
|
||||
char *tmp;
|
||||
|
||||
va_start(ap, format);
|
||||
if (vasprintf(&tmp, format, ap) < 0) {
|
||||
fprintf(stderr, "memory allocation failure\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
va_end(ap);
|
||||
|
||||
buf->append(tmp, strlen(tmp));
|
||||
free(tmp);
|
||||
}
|
||||
|
||||
bool type_and_string::operator<(const type_and_string &o) const {
|
||||
if (string < o.string) {
|
||||
return true;
|
||||
@ -133,11 +104,186 @@ bool type_and_string::operator<(const type_and_string &o) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, int forcetable, const char *attribution, std::map<std::string, layermap_entry> const &layermap) {
|
||||
bool type_and_string::operator!=(const type_and_string &o) const {
|
||||
if (type != o.type) {
|
||||
return true;
|
||||
}
|
||||
if (string != o.string) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void tilestats(std::map<std::string, layermap_entry> const &layermap1, size_t elements, json_writer &state) {
|
||||
// Consolidate layers/attributes whose names are truncated
|
||||
std::vector<std::map<std::string, layermap_entry>> lmv;
|
||||
lmv.push_back(layermap1);
|
||||
std::map<std::string, layermap_entry> layermap = merge_layermaps(lmv, true);
|
||||
|
||||
state.json_write_hash();
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_string("layerCount");
|
||||
state.json_write_unsigned(layermap.size());
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_string("layers");
|
||||
state.json_write_array();
|
||||
|
||||
for (auto layer : layermap) {
|
||||
state.nospace = true;
|
||||
state.json_write_hash();
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_string("layer");
|
||||
state.json_write_string(layer.first);
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_string("count");
|
||||
state.json_write_unsigned(layer.second.points + layer.second.lines + layer.second.polygons);
|
||||
|
||||
std::string geomtype = "Polygon";
|
||||
if (layer.second.points >= layer.second.lines && layer.second.points >= layer.second.polygons) {
|
||||
geomtype = "Point";
|
||||
} else if (layer.second.lines >= layer.second.polygons && layer.second.lines >= layer.second.points) {
|
||||
geomtype = "LineString";
|
||||
}
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_string("geometry");
|
||||
state.json_write_string(geomtype);
|
||||
|
||||
size_t attrib_count = layer.second.file_keys.size();
|
||||
if (attrib_count > max_tilestats_attributes) {
|
||||
attrib_count = max_tilestats_attributes;
|
||||
}
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_string("attributeCount");
|
||||
state.json_write_unsigned(attrib_count);
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_string("attributes");
|
||||
state.nospace = true;
|
||||
state.json_write_array();
|
||||
|
||||
size_t attrs = 0;
|
||||
for (auto attribute : layer.second.file_keys) {
|
||||
if (attrs == elements) {
|
||||
break;
|
||||
}
|
||||
attrs++;
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_hash();
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_string("attribute");
|
||||
state.json_write_string(attribute.first);
|
||||
|
||||
size_t val_count = attribute.second.sample_values.size();
|
||||
if (val_count > max_tilestats_sample_values) {
|
||||
val_count = max_tilestats_sample_values;
|
||||
}
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_string("count");
|
||||
state.json_write_unsigned(val_count);
|
||||
|
||||
int type = 0;
|
||||
for (auto s : attribute.second.sample_values) {
|
||||
type |= (1 << s.type);
|
||||
}
|
||||
|
||||
std::string type_str;
|
||||
// No "null" because null attributes are dropped
|
||||
if (type == (1 << mvt_double)) {
|
||||
type_str = "number";
|
||||
} else if (type == (1 << mvt_bool)) {
|
||||
type_str = "boolean";
|
||||
} else if (type == (1 << mvt_string)) {
|
||||
type_str = "string";
|
||||
} else {
|
||||
type_str = "mixed";
|
||||
}
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_string("type");
|
||||
state.json_write_string(type_str);
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_string("values");
|
||||
state.json_write_array();
|
||||
|
||||
size_t vals = 0;
|
||||
for (auto value : attribute.second.sample_values) {
|
||||
if (vals == elements) {
|
||||
break;
|
||||
}
|
||||
|
||||
state.nospace = true;
|
||||
|
||||
if (value.type == mvt_double || value.type == mvt_bool) {
|
||||
vals++;
|
||||
|
||||
state.json_write_stringified(value.string);
|
||||
} else {
|
||||
std::string trunc = truncate16(value.string, 256);
|
||||
|
||||
if (trunc.size() == value.string.size()) {
|
||||
vals++;
|
||||
|
||||
state.json_write_string(value.string);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
state.nospace = true;
|
||||
state.json_end_array();
|
||||
|
||||
if ((type & (1 << mvt_double)) != 0) {
|
||||
state.nospace = true;
|
||||
state.json_write_string("min");
|
||||
state.json_write_number(attribute.second.min);
|
||||
|
||||
state.nospace = true;
|
||||
state.json_write_string("max");
|
||||
state.json_write_number(attribute.second.max);
|
||||
}
|
||||
|
||||
state.nospace = true;
|
||||
state.json_end_hash();
|
||||
}
|
||||
|
||||
state.nospace = true;
|
||||
state.json_end_array();
|
||||
state.nospace = true;
|
||||
state.json_end_hash();
|
||||
}
|
||||
|
||||
state.nospace = true;
|
||||
state.json_end_array();
|
||||
state.nospace = true;
|
||||
state.json_end_hash();
|
||||
}
|
||||
|
||||
void mbtiles_write_metadata(sqlite3 *outdb, const char *outdir, const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, int forcetable, const char *attribution, std::map<std::string, layermap_entry> const &layermap, bool vector, const char *description, bool do_tilestats, std::map<std::string, std::string> const &attribute_descriptions, std::string const &program, std::string const &commandline) {
|
||||
char *sql, *err;
|
||||
|
||||
sqlite3 *db = outdb;
|
||||
if (outdb == NULL) {
|
||||
if (sqlite3_open("", &db) != SQLITE_OK) {
|
||||
fprintf(stderr, "Temporary db: %s\n", sqlite3_errmsg(db));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (sqlite3_exec(db, "CREATE TABLE metadata (name text, value text);", NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "Create metadata table: %s\n", err);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('name', %Q);", fname);
|
||||
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set name in metadata: %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
@ -145,8 +291,8 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int
|
||||
}
|
||||
sqlite3_free(sql);
|
||||
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('description', %Q);", fname);
|
||||
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('description', %Q);", description != NULL ? description : fname);
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set description in metadata: %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
@ -155,7 +301,7 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int
|
||||
sqlite3_free(sql);
|
||||
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('version', %d);", 2);
|
||||
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set version : %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
@ -164,7 +310,7 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int
|
||||
sqlite3_free(sql);
|
||||
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('minzoom', %d);", minzoom);
|
||||
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set minzoom: %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
@ -173,7 +319,7 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int
|
||||
sqlite3_free(sql);
|
||||
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('maxzoom', %d);", maxzoom);
|
||||
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set maxzoom: %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
@ -182,7 +328,7 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int
|
||||
sqlite3_free(sql);
|
||||
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('center', '%f,%f,%d');", midlon, midlat, maxzoom);
|
||||
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set center: %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
@ -191,7 +337,7 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int
|
||||
sqlite3_free(sql);
|
||||
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('bounds', '%f,%f,%f,%f');", minlon, minlat, maxlon, maxlat);
|
||||
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set bounds: %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
@ -200,7 +346,7 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int
|
||||
sqlite3_free(sql);
|
||||
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('type', %Q);", "overlay");
|
||||
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set type: %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
@ -210,7 +356,7 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int
|
||||
|
||||
if (attribution != NULL) {
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('attribution', %Q);", attribution);
|
||||
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set type: %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
@ -219,8 +365,8 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int
|
||||
sqlite3_free(sql);
|
||||
}
|
||||
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('format', %Q);", "pbf");
|
||||
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('format', %Q);", vector ? "pbf" : "png");
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set format: %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
@ -228,93 +374,247 @@ void mbtiles_write_metadata(sqlite3 *outdb, const char *fname, int minzoom, int
|
||||
}
|
||||
sqlite3_free(sql);
|
||||
|
||||
std::string buf("{");
|
||||
aprintf(&buf, "\"vector_layers\": [ ");
|
||||
|
||||
std::vector<std::string> lnames;
|
||||
for (auto ai = layermap.begin(); ai != layermap.end(); ++ai) {
|
||||
lnames.push_back(ai->first);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < lnames.size(); i++) {
|
||||
if (i != 0) {
|
||||
aprintf(&buf, ", ");
|
||||
}
|
||||
|
||||
auto fk = layermap.find(lnames[i]);
|
||||
aprintf(&buf, "{ \"id\": \"");
|
||||
quote(&buf, lnames[i].c_str());
|
||||
aprintf(&buf, "\", \"description\": \"\", \"minzoom\": %d, \"maxzoom\": %d, \"fields\": {", fk->second.minzoom, fk->second.maxzoom);
|
||||
|
||||
std::set<type_and_string>::iterator j;
|
||||
bool first = true;
|
||||
for (j = fk->second.file_keys.begin(); j != fk->second.file_keys.end(); ++j) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
aprintf(&buf, ", ");
|
||||
}
|
||||
|
||||
aprintf(&buf, "\"");
|
||||
quote(&buf, j->string.c_str());
|
||||
|
||||
if (j->type == VT_NUMBER) {
|
||||
aprintf(&buf, "\": \"Number\"");
|
||||
} else if (j->type == VT_BOOLEAN) {
|
||||
aprintf(&buf, "\": \"Boolean\"");
|
||||
} else {
|
||||
aprintf(&buf, "\": \"String\"");
|
||||
}
|
||||
}
|
||||
|
||||
aprintf(&buf, "} }");
|
||||
}
|
||||
|
||||
aprintf(&buf, " ] }");
|
||||
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('json', %Q);", buf.c_str());
|
||||
if (sqlite3_exec(outdb, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set json: %s\n", err);
|
||||
std::string version = program + " " + VERSION;
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('generator', %Q);", version.c_str());
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set version: %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
sqlite3_free(sql);
|
||||
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('generator_options', %Q);", commandline.c_str());
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set commandline: %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
sqlite3_free(sql);
|
||||
|
||||
if (vector) {
|
||||
size_t elements = max_tilestats_values;
|
||||
std::string buf;
|
||||
|
||||
{
|
||||
json_writer state(&buf);
|
||||
|
||||
state.json_write_hash();
|
||||
state.nospace = true;
|
||||
|
||||
state.json_write_string("vector_layers");
|
||||
state.json_write_array();
|
||||
|
||||
std::vector<std::string> lnames;
|
||||
for (auto ai = layermap.begin(); ai != layermap.end(); ++ai) {
|
||||
lnames.push_back(ai->first);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < lnames.size(); i++) {
|
||||
auto fk = layermap.find(lnames[i]);
|
||||
state.json_write_hash();
|
||||
|
||||
state.json_write_string("id");
|
||||
state.json_write_string(lnames[i]);
|
||||
|
||||
state.json_write_string("description");
|
||||
state.json_write_string(fk->second.description);
|
||||
|
||||
state.json_write_string("minzoom");
|
||||
state.json_write_signed(fk->second.minzoom);
|
||||
|
||||
state.json_write_string("maxzoom");
|
||||
state.json_write_signed(fk->second.maxzoom);
|
||||
|
||||
state.json_write_string("fields");
|
||||
state.json_write_hash();
|
||||
state.nospace = true;
|
||||
|
||||
bool first = true;
|
||||
for (auto j = fk->second.file_keys.begin(); j != fk->second.file_keys.end(); ++j) {
|
||||
if (first) {
|
||||
first = false;
|
||||
}
|
||||
|
||||
state.json_write_string(j->first);
|
||||
|
||||
auto f = attribute_descriptions.find(j->first);
|
||||
if (f == attribute_descriptions.end()) {
|
||||
int type = 0;
|
||||
for (auto s : j->second.sample_values) {
|
||||
type |= (1 << s.type);
|
||||
}
|
||||
|
||||
if (type == (1 << mvt_double)) {
|
||||
state.json_write_string("Number");
|
||||
} else if (type == (1 << mvt_bool)) {
|
||||
state.json_write_string("Boolean");
|
||||
} else if (type == (1 << mvt_string)) {
|
||||
state.json_write_string("String");
|
||||
} else {
|
||||
state.json_write_string("Mixed");
|
||||
}
|
||||
} else {
|
||||
state.json_write_string(f->second);
|
||||
}
|
||||
}
|
||||
|
||||
state.nospace = true;
|
||||
state.json_end_hash();
|
||||
state.json_end_hash();
|
||||
}
|
||||
|
||||
state.json_end_array();
|
||||
|
||||
if (do_tilestats && elements > 0) {
|
||||
state.nospace = true;
|
||||
state.json_write_string("tilestats");
|
||||
tilestats(layermap, elements, state);
|
||||
}
|
||||
|
||||
state.nospace = true;
|
||||
state.json_end_hash();
|
||||
}
|
||||
|
||||
sql = sqlite3_mprintf("INSERT INTO metadata (name, value) VALUES ('json', %Q);", buf.c_str());
|
||||
if (sqlite3_exec(db, sql, NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "set json: %s\n", err);
|
||||
if (!forcetable) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
sqlite3_free(sql);
|
||||
}
|
||||
|
||||
if (outdir != NULL) {
|
||||
std::string metadata = std::string(outdir) + "/metadata.json";
|
||||
|
||||
struct stat st;
|
||||
if (stat(metadata.c_str(), &st) == 0) {
|
||||
// Leave existing metadata in place with --allow-existing
|
||||
} else {
|
||||
FILE *fp = fopen(metadata.c_str(), "w");
|
||||
if (fp == NULL) {
|
||||
perror(metadata.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
json_writer state(fp);
|
||||
|
||||
state.json_write_hash();
|
||||
state.json_write_newline();
|
||||
|
||||
sqlite3_stmt *stmt;
|
||||
if (sqlite3_prepare_v2(db, "SELECT name, value from metadata;", -1, &stmt, NULL) == SQLITE_OK) {
|
||||
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||
std::string key, value;
|
||||
|
||||
const char *k = (const char *) sqlite3_column_text(stmt, 0);
|
||||
const char *v = (const char *) sqlite3_column_text(stmt, 1);
|
||||
if (k == NULL || v == NULL) {
|
||||
fprintf(stderr, "Corrupt mbtiles file: null metadata\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
state.json_comma_newline();
|
||||
state.json_write_string(k);
|
||||
state.json_write_string(v);
|
||||
}
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
|
||||
state.json_write_newline();
|
||||
state.json_end_hash();
|
||||
state.json_write_newline();
|
||||
fclose(fp);
|
||||
}
|
||||
}
|
||||
|
||||
if (outdb == NULL) {
|
||||
if (sqlite3_close(db) != SQLITE_OK) {
|
||||
fprintf(stderr, "Could not close temp database: %s\n", sqlite3_errmsg(db));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mbtiles_close(sqlite3 *outdb, char **argv) {
|
||||
void mbtiles_close(sqlite3 *outdb, const char *pgm) {
|
||||
char *err;
|
||||
|
||||
if (sqlite3_exec(outdb, "ANALYZE;", NULL, NULL, &err) != SQLITE_OK) {
|
||||
fprintf(stderr, "%s: ANALYZE failed: %s\n", argv[0], err);
|
||||
fprintf(stderr, "%s: ANALYZE failed: %s\n", pgm, err);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (sqlite3_close(outdb) != SQLITE_OK) {
|
||||
fprintf(stderr, "%s: could not close database: %s\n", argv[0], sqlite3_errmsg(outdb));
|
||||
fprintf(stderr, "%s: could not close database: %s\n", pgm, sqlite3_errmsg(outdb));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
std::map<std::string, layermap_entry> merge_layermaps(std::vector<std::map<std::string, layermap_entry> > const &maps) {
|
||||
std::map<std::string, layermap_entry> merge_layermaps(std::vector<std::map<std::string, layermap_entry>> const &maps) {
|
||||
return merge_layermaps(maps, false);
|
||||
}
|
||||
|
||||
std::map<std::string, layermap_entry> merge_layermaps(std::vector<std::map<std::string, layermap_entry>> const &maps, bool trunc) {
|
||||
std::map<std::string, layermap_entry> out;
|
||||
|
||||
for (size_t i = 0; i < maps.size(); i++) {
|
||||
for (auto map = maps[i].begin(); map != maps[i].end(); ++map) {
|
||||
if (out.count(map->first) == 0) {
|
||||
out.insert(std::pair<std::string, layermap_entry>(map->first, layermap_entry(out.size())));
|
||||
auto out_entry = out.find(map->first);
|
||||
out_entry->second.minzoom = map->second.minzoom;
|
||||
out_entry->second.maxzoom = map->second.maxzoom;
|
||||
if (map->second.points + map->second.lines + map->second.polygons + map->second.retain == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto out_entry = out.find(map->first);
|
||||
std::string layername = map->first;
|
||||
if (trunc) {
|
||||
layername = truncate16(layername, 256);
|
||||
}
|
||||
|
||||
if (out.count(layername) == 0) {
|
||||
out.insert(std::pair<std::string, layermap_entry>(layername, layermap_entry(out.size())));
|
||||
auto out_entry = out.find(layername);
|
||||
out_entry->second.minzoom = map->second.minzoom;
|
||||
out_entry->second.maxzoom = map->second.maxzoom;
|
||||
out_entry->second.description = map->second.description;
|
||||
}
|
||||
|
||||
auto out_entry = out.find(layername);
|
||||
if (out_entry == out.end()) {
|
||||
fprintf(stderr, "Internal error merging layers\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
for (auto fk = map->second.file_keys.begin(); fk != map->second.file_keys.end(); ++fk) {
|
||||
out_entry->second.file_keys.insert(*fk);
|
||||
std::string attribname = fk->first;
|
||||
if (trunc) {
|
||||
attribname = truncate16(attribname, 256);
|
||||
}
|
||||
|
||||
auto fk2 = out_entry->second.file_keys.find(attribname);
|
||||
|
||||
if (fk2 == out_entry->second.file_keys.end()) {
|
||||
out_entry->second.file_keys.insert(std::pair<std::string, type_and_string_stats>(attribname, fk->second));
|
||||
} else {
|
||||
for (auto val : fk->second.sample_values) {
|
||||
auto pt = std::lower_bound(fk2->second.sample_values.begin(), fk2->second.sample_values.end(), val);
|
||||
if (pt == fk2->second.sample_values.end() || *pt != val) { // not found
|
||||
fk2->second.sample_values.insert(pt, val);
|
||||
|
||||
if (fk2->second.sample_values.size() > max_tilestats_sample_values) {
|
||||
fk2->second.sample_values.pop_back();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fk2->second.type |= fk->second.type;
|
||||
|
||||
if (fk->second.min < fk2->second.min) {
|
||||
fk2->second.min = fk->second.min;
|
||||
}
|
||||
if (fk->second.max > fk2->second.max) {
|
||||
fk2->second.max = fk->second.max;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (map->second.minzoom < out_entry->second.minzoom) {
|
||||
@ -323,8 +623,51 @@ std::map<std::string, layermap_entry> merge_layermaps(std::vector<std::map<std::
|
||||
if (map->second.maxzoom > out_entry->second.maxzoom) {
|
||||
out_entry->second.maxzoom = map->second.maxzoom;
|
||||
}
|
||||
|
||||
out_entry->second.points += map->second.points;
|
||||
out_entry->second.lines += map->second.lines;
|
||||
out_entry->second.polygons += map->second.polygons;
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void add_to_file_keys(std::map<std::string, type_and_string_stats> &file_keys, std::string const &attrib, type_and_string const &val) {
|
||||
if (val.type == mvt_null) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto fka = file_keys.find(attrib);
|
||||
if (fka == file_keys.end()) {
|
||||
file_keys.insert(std::pair<std::string, type_and_string_stats>(attrib, type_and_string_stats()));
|
||||
fka = file_keys.find(attrib);
|
||||
}
|
||||
|
||||
if (fka == file_keys.end()) {
|
||||
fprintf(stderr, "Can't happen (tilestats)\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (val.type == mvt_double) {
|
||||
double d = atof(val.string.c_str());
|
||||
|
||||
if (d < fka->second.min) {
|
||||
fka->second.min = d;
|
||||
}
|
||||
if (d > fka->second.max) {
|
||||
fka->second.max = d;
|
||||
}
|
||||
}
|
||||
|
||||
auto pt = std::lower_bound(fka->second.sample_values.begin(), fka->second.sample_values.end(), val);
|
||||
if (pt == fka->second.sample_values.end() || *pt != val) { // not found
|
||||
fka->second.sample_values.insert(pt, val);
|
||||
|
||||
if (fka->second.sample_values.size() > max_tilestats_sample_values) {
|
||||
fka->second.sample_values.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
fka->second.type |= (1 << val.type);
|
||||
}
|
||||
|
48
mbtiles.hpp
48
mbtiles.hpp
@ -1,15 +1,40 @@
|
||||
#ifndef MBTILES_HPP
|
||||
#define MBTILES_HPP
|
||||
|
||||
#include <math.h>
|
||||
#include <map>
|
||||
#include "mvt.hpp"
|
||||
|
||||
extern size_t max_tilestats_attributes;
|
||||
extern size_t max_tilestats_sample_values;
|
||||
extern size_t max_tilestats_values;
|
||||
|
||||
struct type_and_string {
|
||||
int type;
|
||||
std::string string;
|
||||
int type = 0;
|
||||
std::string string = "";
|
||||
|
||||
bool operator<(const type_and_string &o) const;
|
||||
bool operator!=(const type_and_string &o) const;
|
||||
};
|
||||
|
||||
struct type_and_string_stats {
|
||||
std::vector<type_and_string> sample_values = std::vector<type_and_string>(); // sorted
|
||||
double min = INFINITY;
|
||||
double max = -INFINITY;
|
||||
int type = 0;
|
||||
};
|
||||
|
||||
struct layermap_entry {
|
||||
size_t id;
|
||||
std::set<type_and_string> file_keys;
|
||||
int minzoom;
|
||||
int maxzoom;
|
||||
size_t id = 0;
|
||||
std::map<std::string, type_and_string_stats> file_keys{};
|
||||
int minzoom = 0;
|
||||
int maxzoom = 0;
|
||||
std::string description = "";
|
||||
|
||||
size_t points = 0;
|
||||
size_t lines = 0;
|
||||
size_t polygons = 0;
|
||||
size_t retain = 0; // keep for tilestats, even if no features directly here
|
||||
|
||||
layermap_entry(size_t _id) {
|
||||
id = _id;
|
||||
@ -20,10 +45,13 @@ sqlite3 *mbtiles_open(char *dbname, char **argv, int forcetable);
|
||||
|
||||
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, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, int forcetable, const char *attribution, std::map<std::string, layermap_entry> const &layermap);
|
||||
void mbtiles_write_metadata(sqlite3 *outdb, const char *outdir, const char *fname, int minzoom, int maxzoom, double minlat, double minlon, double maxlat, double maxlon, double midlat, double midlon, int forcetable, const char *attribution, std::map<std::string, layermap_entry> const &layermap, bool vector, const char *description, bool do_tilestats, std::map<std::string, std::string> const &attribute_descriptions, std::string const &program, std::string const &commandline);
|
||||
|
||||
void mbtiles_close(sqlite3 *outdb, char **argv);
|
||||
|
||||
void aprintf(std::string *buf, const char *format, ...);
|
||||
void mbtiles_close(sqlite3 *outdb, const char *pgm);
|
||||
|
||||
std::map<std::string, layermap_entry> merge_layermaps(std::vector<std::map<std::string, layermap_entry> > const &maps);
|
||||
std::map<std::string, layermap_entry> merge_layermaps(std::vector<std::map<std::string, layermap_entry> > const &maps, bool trunc);
|
||||
|
||||
void add_to_file_keys(std::map<std::string, type_and_string_stats> &file_keys, std::string const &layername, type_and_string const &val);
|
||||
|
||||
#endif
|
||||
|
@ -53,7 +53,7 @@ int memfile_write(struct memfile *file, void *s, long long len) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
file->len += INCREMENT;
|
||||
file->len += (len + INCREMENT + 1) / INCREMENT * INCREMENT;
|
||||
|
||||
if (ftruncate(file->fd, file->len) != 0) {
|
||||
return -1;
|
||||
|
21
memfile.hpp
21
memfile.hpp
@ -1,11 +1,22 @@
|
||||
#ifndef MEMFILE_HPP
|
||||
#define MEMFILE_HPP
|
||||
|
||||
#include <atomic>
|
||||
|
||||
struct memfile {
|
||||
int fd;
|
||||
char *map;
|
||||
long long len;
|
||||
long long off;
|
||||
long long tree;
|
||||
int fd = 0;
|
||||
char *map = NULL;
|
||||
std::atomic<long long> len;
|
||||
long long off = 0;
|
||||
unsigned long tree = 0;
|
||||
|
||||
memfile()
|
||||
: len(0) {
|
||||
}
|
||||
};
|
||||
|
||||
struct memfile *memfile_open(int fd);
|
||||
int memfile_close(struct memfile *file);
|
||||
int memfile_write(struct memfile *file, void *s, long long len);
|
||||
|
||||
#endif
|
||||
|
19
milo/LICENSE.txt
Normal file
19
milo/LICENSE.txt
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (C) 2014 Milo Yip
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
415
milo/dtoa_milo.h
Normal file
415
milo/dtoa_milo.h
Normal file
@ -0,0 +1,415 @@
|
||||
#pragma once
|
||||
#include <assert.h>
|
||||
#include <math.h>
|
||||
#include <cmath>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include "msinttypes/stdint.h"
|
||||
#include <intrin.h>
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#endif
|
||||
|
||||
namespace milo {
|
||||
|
||||
#define UINT64_C2(h, l) ((static_cast<uint64_t>(h) << 32) | static_cast<uint64_t>(l))
|
||||
|
||||
struct DiyFp {
|
||||
DiyFp() {}
|
||||
|
||||
DiyFp(uint64_t ff, int ee) : f(ff), e(ee) {}
|
||||
|
||||
DiyFp(double d) {
|
||||
union {
|
||||
double d;
|
||||
uint64_t u64;
|
||||
} u = { d };
|
||||
|
||||
int biased_e = (u.u64 & kDpExponentMask) >> kDpSignificandSize;
|
||||
uint64_t significand = (u.u64 & kDpSignificandMask);
|
||||
if (biased_e != 0) {
|
||||
f = significand + kDpHiddenBit;
|
||||
e = biased_e - kDpExponentBias;
|
||||
}
|
||||
else {
|
||||
f = significand;
|
||||
e = kDpMinExponent + 1;
|
||||
}
|
||||
}
|
||||
|
||||
DiyFp operator-(const DiyFp& rhs) const {
|
||||
assert(e == rhs.e);
|
||||
assert(f >= rhs.f);
|
||||
return DiyFp(f - rhs.f, e);
|
||||
}
|
||||
|
||||
DiyFp operator*(const DiyFp& rhs) const {
|
||||
#if defined(_MSC_VER) && defined(_M_AMD64)
|
||||
uint64_t h;
|
||||
uint64_t l = _umul128(f, rhs.f, &h);
|
||||
if (l & (uint64_t(1) << 63)) // rounding
|
||||
h++;
|
||||
return DiyFp(h, e + rhs.e + 64);
|
||||
#elif (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) && defined(__x86_64__)
|
||||
unsigned __int128 p = static_cast<unsigned __int128>(f) * static_cast<unsigned __int128>(rhs.f);
|
||||
uint64_t h = p >> 64;
|
||||
uint64_t l = static_cast<uint64_t>(p);
|
||||
if (l & (uint64_t(1) << 63)) // rounding
|
||||
h++;
|
||||
return DiyFp(h, e + rhs.e + 64);
|
||||
#else
|
||||
const uint64_t M32 = 0xFFFFFFFF;
|
||||
const uint64_t a = f >> 32;
|
||||
const uint64_t b = f & M32;
|
||||
const uint64_t c = rhs.f >> 32;
|
||||
const uint64_t d = rhs.f & M32;
|
||||
const uint64_t ac = a * c;
|
||||
const uint64_t bc = b * c;
|
||||
const uint64_t ad = a * d;
|
||||
const uint64_t bd = b * d;
|
||||
uint64_t tmp = (bd >> 32) + (ad & M32) + (bc & M32);
|
||||
tmp += 1U << 31; /// mult_round
|
||||
return DiyFp(ac + (ad >> 32) + (bc >> 32) + (tmp >> 32), e + rhs.e + 64);
|
||||
#endif
|
||||
}
|
||||
|
||||
DiyFp Normalize() const {
|
||||
#if defined(_MSC_VER) && defined(_M_AMD64)
|
||||
unsigned long index;
|
||||
_BitScanReverse64(&index, f);
|
||||
return DiyFp(f << (63 - index), e - (63 - index));
|
||||
#elif defined(__GNUC__)
|
||||
int s = __builtin_clzll(f);
|
||||
return DiyFp(f << s, e - s);
|
||||
#else
|
||||
DiyFp res = *this;
|
||||
while (!(res.f & kDpHiddenBit)) {
|
||||
res.f <<= 1;
|
||||
res.e--;
|
||||
}
|
||||
res.f <<= (kDiySignificandSize - kDpSignificandSize - 1);
|
||||
res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 1);
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
DiyFp NormalizeBoundary() const {
|
||||
#if defined(_MSC_VER) && defined(_M_AMD64)
|
||||
unsigned long index;
|
||||
_BitScanReverse64(&index, f);
|
||||
return DiyFp (f << (63 - index), e - (63 - index));
|
||||
#else
|
||||
DiyFp res = *this;
|
||||
while (!(res.f & (kDpHiddenBit << 1))) {
|
||||
res.f <<= 1;
|
||||
res.e--;
|
||||
}
|
||||
res.f <<= (kDiySignificandSize - kDpSignificandSize - 2);
|
||||
res.e = res.e - (kDiySignificandSize - kDpSignificandSize - 2);
|
||||
return res;
|
||||
#endif
|
||||
}
|
||||
|
||||
void NormalizedBoundaries(DiyFp* minus, DiyFp* plus) const {
|
||||
DiyFp pl = DiyFp((f << 1) + 1, e - 1).NormalizeBoundary();
|
||||
DiyFp mi = (f == kDpHiddenBit) ? DiyFp((f << 2) - 1, e - 2) : DiyFp((f << 1) - 1, e - 1);
|
||||
mi.f <<= mi.e - pl.e;
|
||||
mi.e = pl.e;
|
||||
*plus = pl;
|
||||
*minus = mi;
|
||||
}
|
||||
|
||||
static const int kDiySignificandSize = 64;
|
||||
static const int kDpSignificandSize = 52;
|
||||
static const int kDpExponentBias = 0x3FF + kDpSignificandSize;
|
||||
static const int kDpMinExponent = -kDpExponentBias;
|
||||
static const uint64_t kDpExponentMask = UINT64_C2(0x7FF00000, 0x00000000);
|
||||
static const uint64_t kDpSignificandMask = UINT64_C2(0x000FFFFF, 0xFFFFFFFF);
|
||||
static const uint64_t kDpHiddenBit = UINT64_C2(0x00100000, 0x00000000);
|
||||
|
||||
uint64_t f;
|
||||
int e;
|
||||
};
|
||||
|
||||
inline DiyFp GetCachedPower(int e, int* K) {
|
||||
// 10^-348, 10^-340, ..., 10^340
|
||||
static const uint64_t kCachedPowers_F[] = {
|
||||
UINT64_C2(0xfa8fd5a0, 0x081c0288), UINT64_C2(0xbaaee17f, 0xa23ebf76),
|
||||
UINT64_C2(0x8b16fb20, 0x3055ac76), UINT64_C2(0xcf42894a, 0x5dce35ea),
|
||||
UINT64_C2(0x9a6bb0aa, 0x55653b2d), UINT64_C2(0xe61acf03, 0x3d1a45df),
|
||||
UINT64_C2(0xab70fe17, 0xc79ac6ca), UINT64_C2(0xff77b1fc, 0xbebcdc4f),
|
||||
UINT64_C2(0xbe5691ef, 0x416bd60c), UINT64_C2(0x8dd01fad, 0x907ffc3c),
|
||||
UINT64_C2(0xd3515c28, 0x31559a83), UINT64_C2(0x9d71ac8f, 0xada6c9b5),
|
||||
UINT64_C2(0xea9c2277, 0x23ee8bcb), UINT64_C2(0xaecc4991, 0x4078536d),
|
||||
UINT64_C2(0x823c1279, 0x5db6ce57), UINT64_C2(0xc2109436, 0x4dfb5637),
|
||||
UINT64_C2(0x9096ea6f, 0x3848984f), UINT64_C2(0xd77485cb, 0x25823ac7),
|
||||
UINT64_C2(0xa086cfcd, 0x97bf97f4), UINT64_C2(0xef340a98, 0x172aace5),
|
||||
UINT64_C2(0xb23867fb, 0x2a35b28e), UINT64_C2(0x84c8d4df, 0xd2c63f3b),
|
||||
UINT64_C2(0xc5dd4427, 0x1ad3cdba), UINT64_C2(0x936b9fce, 0xbb25c996),
|
||||
UINT64_C2(0xdbac6c24, 0x7d62a584), UINT64_C2(0xa3ab6658, 0x0d5fdaf6),
|
||||
UINT64_C2(0xf3e2f893, 0xdec3f126), UINT64_C2(0xb5b5ada8, 0xaaff80b8),
|
||||
UINT64_C2(0x87625f05, 0x6c7c4a8b), UINT64_C2(0xc9bcff60, 0x34c13053),
|
||||
UINT64_C2(0x964e858c, 0x91ba2655), UINT64_C2(0xdff97724, 0x70297ebd),
|
||||
UINT64_C2(0xa6dfbd9f, 0xb8e5b88f), UINT64_C2(0xf8a95fcf, 0x88747d94),
|
||||
UINT64_C2(0xb9447093, 0x8fa89bcf), UINT64_C2(0x8a08f0f8, 0xbf0f156b),
|
||||
UINT64_C2(0xcdb02555, 0x653131b6), UINT64_C2(0x993fe2c6, 0xd07b7fac),
|
||||
UINT64_C2(0xe45c10c4, 0x2a2b3b06), UINT64_C2(0xaa242499, 0x697392d3),
|
||||
UINT64_C2(0xfd87b5f2, 0x8300ca0e), UINT64_C2(0xbce50864, 0x92111aeb),
|
||||
UINT64_C2(0x8cbccc09, 0x6f5088cc), UINT64_C2(0xd1b71758, 0xe219652c),
|
||||
UINT64_C2(0x9c400000, 0x00000000), UINT64_C2(0xe8d4a510, 0x00000000),
|
||||
UINT64_C2(0xad78ebc5, 0xac620000), UINT64_C2(0x813f3978, 0xf8940984),
|
||||
UINT64_C2(0xc097ce7b, 0xc90715b3), UINT64_C2(0x8f7e32ce, 0x7bea5c70),
|
||||
UINT64_C2(0xd5d238a4, 0xabe98068), UINT64_C2(0x9f4f2726, 0x179a2245),
|
||||
UINT64_C2(0xed63a231, 0xd4c4fb27), UINT64_C2(0xb0de6538, 0x8cc8ada8),
|
||||
UINT64_C2(0x83c7088e, 0x1aab65db), UINT64_C2(0xc45d1df9, 0x42711d9a),
|
||||
UINT64_C2(0x924d692c, 0xa61be758), UINT64_C2(0xda01ee64, 0x1a708dea),
|
||||
UINT64_C2(0xa26da399, 0x9aef774a), UINT64_C2(0xf209787b, 0xb47d6b85),
|
||||
UINT64_C2(0xb454e4a1, 0x79dd1877), UINT64_C2(0x865b8692, 0x5b9bc5c2),
|
||||
UINT64_C2(0xc83553c5, 0xc8965d3d), UINT64_C2(0x952ab45c, 0xfa97a0b3),
|
||||
UINT64_C2(0xde469fbd, 0x99a05fe3), UINT64_C2(0xa59bc234, 0xdb398c25),
|
||||
UINT64_C2(0xf6c69a72, 0xa3989f5c), UINT64_C2(0xb7dcbf53, 0x54e9bece),
|
||||
UINT64_C2(0x88fcf317, 0xf22241e2), UINT64_C2(0xcc20ce9b, 0xd35c78a5),
|
||||
UINT64_C2(0x98165af3, 0x7b2153df), UINT64_C2(0xe2a0b5dc, 0x971f303a),
|
||||
UINT64_C2(0xa8d9d153, 0x5ce3b396), UINT64_C2(0xfb9b7cd9, 0xa4a7443c),
|
||||
UINT64_C2(0xbb764c4c, 0xa7a44410), UINT64_C2(0x8bab8eef, 0xb6409c1a),
|
||||
UINT64_C2(0xd01fef10, 0xa657842c), UINT64_C2(0x9b10a4e5, 0xe9913129),
|
||||
UINT64_C2(0xe7109bfb, 0xa19c0c9d), UINT64_C2(0xac2820d9, 0x623bf429),
|
||||
UINT64_C2(0x80444b5e, 0x7aa7cf85), UINT64_C2(0xbf21e440, 0x03acdd2d),
|
||||
UINT64_C2(0x8e679c2f, 0x5e44ff8f), UINT64_C2(0xd433179d, 0x9c8cb841),
|
||||
UINT64_C2(0x9e19db92, 0xb4e31ba9), UINT64_C2(0xeb96bf6e, 0xbadf77d9),
|
||||
UINT64_C2(0xaf87023b, 0x9bf0ee6b)
|
||||
};
|
||||
static const int16_t kCachedPowers_E[] = {
|
||||
-1220, -1193, -1166, -1140, -1113, -1087, -1060, -1034, -1007, -980,
|
||||
-954, -927, -901, -874, -847, -821, -794, -768, -741, -715,
|
||||
-688, -661, -635, -608, -582, -555, -529, -502, -475, -449,
|
||||
-422, -396, -369, -343, -316, -289, -263, -236, -210, -183,
|
||||
-157, -130, -103, -77, -50, -24, 3, 30, 56, 83,
|
||||
109, 136, 162, 189, 216, 242, 269, 295, 322, 348,
|
||||
375, 402, 428, 455, 481, 508, 534, 561, 588, 614,
|
||||
641, 667, 694, 720, 747, 774, 800, 827, 853, 880,
|
||||
907, 933, 960, 986, 1013, 1039, 1066
|
||||
};
|
||||
|
||||
//int k = static_cast<int>(ceil((-61 - e) * 0.30102999566398114)) + 374;
|
||||
double dk = (-61 - e) * 0.30102999566398114 + 347; // dk must be positive, so can do ceiling in positive
|
||||
int k = static_cast<int>(dk);
|
||||
if (k != dk)
|
||||
k++;
|
||||
|
||||
unsigned index = static_cast<unsigned>((k >> 3) + 1);
|
||||
*K = -(-348 + static_cast<int>(index << 3)); // decimal exponent no need lookup table
|
||||
|
||||
assert(index < sizeof(kCachedPowers_F) / sizeof(kCachedPowers_F[0]));
|
||||
return DiyFp(kCachedPowers_F[index], kCachedPowers_E[index]);
|
||||
}
|
||||
|
||||
inline void GrisuRound(std::string &buffer, int len, uint64_t delta, uint64_t rest, uint64_t ten_kappa, uint64_t wp_w) {
|
||||
while (rest < wp_w && delta - rest >= ten_kappa &&
|
||||
(rest + ten_kappa < wp_w || /// closer
|
||||
wp_w - rest > rest + ten_kappa - wp_w)) {
|
||||
buffer[len - 1]--;
|
||||
rest += ten_kappa;
|
||||
}
|
||||
}
|
||||
|
||||
inline unsigned CountDecimalDigit32(uint32_t n) {
|
||||
// Simple pure C++ implementation was faster than __builtin_clz version in this situation.
|
||||
if (n < 10) return 1;
|
||||
if (n < 100) return 2;
|
||||
if (n < 1000) return 3;
|
||||
if (n < 10000) return 4;
|
||||
if (n < 100000) return 5;
|
||||
if (n < 1000000) return 6;
|
||||
if (n < 10000000) return 7;
|
||||
if (n < 100000000) return 8;
|
||||
if (n < 1000000000) return 9;
|
||||
return 10;
|
||||
}
|
||||
|
||||
inline void DigitGen(const DiyFp& W, const DiyFp& Mp, uint64_t delta, std::string &buffer, int* len, int* K) {
|
||||
static const uint32_t kPow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
|
||||
const DiyFp one(uint64_t(1) << -Mp.e, Mp.e);
|
||||
const DiyFp wp_w = Mp - W;
|
||||
uint32_t p1 = static_cast<uint32_t>(Mp.f >> -one.e);
|
||||
uint64_t p2 = Mp.f & (one.f - 1);
|
||||
int kappa = static_cast<int>(CountDecimalDigit32(p1));
|
||||
*len = 0;
|
||||
|
||||
while (kappa > 0) {
|
||||
uint32_t d;
|
||||
switch (kappa) {
|
||||
case 10: d = p1 / 1000000000; p1 %= 1000000000; break;
|
||||
case 9: d = p1 / 100000000; p1 %= 100000000; break;
|
||||
case 8: d = p1 / 10000000; p1 %= 10000000; break;
|
||||
case 7: d = p1 / 1000000; p1 %= 1000000; break;
|
||||
case 6: d = p1 / 100000; p1 %= 100000; break;
|
||||
case 5: d = p1 / 10000; p1 %= 10000; break;
|
||||
case 4: d = p1 / 1000; p1 %= 1000; break;
|
||||
case 3: d = p1 / 100; p1 %= 100; break;
|
||||
case 2: d = p1 / 10; p1 %= 10; break;
|
||||
case 1: d = p1; p1 = 0; break;
|
||||
default:
|
||||
#if defined(_MSC_VER)
|
||||
__assume(0);
|
||||
#elif __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 5)
|
||||
__builtin_unreachable();
|
||||
#else
|
||||
d = 0;
|
||||
#endif
|
||||
}
|
||||
if (d || *len) {
|
||||
buffer.push_back('0' + static_cast<char>(d));
|
||||
(*len)++;
|
||||
}
|
||||
kappa--;
|
||||
uint64_t tmp = (static_cast<uint64_t>(p1) << -one.e) + p2;
|
||||
if (tmp <= delta) {
|
||||
*K += kappa;
|
||||
GrisuRound(buffer, *len, delta, tmp, static_cast<uint64_t>(kPow10[kappa]) << -one.e, wp_w.f);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// kappa = 0
|
||||
for (;;) {
|
||||
p2 *= 10;
|
||||
delta *= 10;
|
||||
char d = static_cast<char>(p2 >> -one.e);
|
||||
if (d || *len) {
|
||||
buffer.push_back('0' + d);
|
||||
(*len)++;
|
||||
}
|
||||
p2 &= one.f - 1;
|
||||
kappa--;
|
||||
if (p2 < delta) {
|
||||
*K += kappa;
|
||||
int index = -static_cast<int>(kappa);
|
||||
GrisuRound(buffer, *len, delta, p2, one.f, wp_w.f * (index < 9 ? kPow10[-static_cast<int>(kappa)] : 0));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void Grisu2(double value, std::string &buffer, int* length, int* K) {
|
||||
const DiyFp v(value);
|
||||
DiyFp w_m, w_p;
|
||||
v.NormalizedBoundaries(&w_m, &w_p);
|
||||
|
||||
const DiyFp c_mk = GetCachedPower(w_p.e, K);
|
||||
const DiyFp W = v.Normalize() * c_mk;
|
||||
DiyFp Wp = w_p * c_mk;
|
||||
DiyFp Wm = w_m * c_mk;
|
||||
Wm.f++;
|
||||
Wp.f--;
|
||||
DigitGen(W, Wp, Wp.f - Wm.f, buffer, length, K);
|
||||
}
|
||||
|
||||
inline const char* GetDigitsLut() {
|
||||
static const char cDigitsLut[200] = {
|
||||
'0', '0', '0', '1', '0', '2', '0', '3', '0', '4', '0', '5', '0', '6', '0', '7', '0', '8', '0', '9',
|
||||
'1', '0', '1', '1', '1', '2', '1', '3', '1', '4', '1', '5', '1', '6', '1', '7', '1', '8', '1', '9',
|
||||
'2', '0', '2', '1', '2', '2', '2', '3', '2', '4', '2', '5', '2', '6', '2', '7', '2', '8', '2', '9',
|
||||
'3', '0', '3', '1', '3', '2', '3', '3', '3', '4', '3', '5', '3', '6', '3', '7', '3', '8', '3', '9',
|
||||
'4', '0', '4', '1', '4', '2', '4', '3', '4', '4', '4', '5', '4', '6', '4', '7', '4', '8', '4', '9',
|
||||
'5', '0', '5', '1', '5', '2', '5', '3', '5', '4', '5', '5', '5', '6', '5', '7', '5', '8', '5', '9',
|
||||
'6', '0', '6', '1', '6', '2', '6', '3', '6', '4', '6', '5', '6', '6', '6', '7', '6', '8', '6', '9',
|
||||
'7', '0', '7', '1', '7', '2', '7', '3', '7', '4', '7', '5', '7', '6', '7', '7', '7', '8', '7', '9',
|
||||
'8', '0', '8', '1', '8', '2', '8', '3', '8', '4', '8', '5', '8', '6', '8', '7', '8', '8', '8', '9',
|
||||
'9', '0', '9', '1', '9', '2', '9', '3', '9', '4', '9', '5', '9', '6', '9', '7', '9', '8', '9', '9'
|
||||
};
|
||||
return cDigitsLut;
|
||||
}
|
||||
|
||||
inline void WriteExponent(int K, std::string &buffer) {
|
||||
if (K < 0) {
|
||||
buffer.push_back('-');
|
||||
K = -K;
|
||||
} else {
|
||||
buffer.push_back('+');
|
||||
}
|
||||
|
||||
if (K >= 100) {
|
||||
buffer.push_back('0' + static_cast<char>(K / 100));
|
||||
K %= 100;
|
||||
const char* d = GetDigitsLut() + K * 2;
|
||||
buffer.push_back(d[0]);
|
||||
buffer.push_back(d[1]);
|
||||
}
|
||||
else if (K >= 10) {
|
||||
const char* d = GetDigitsLut() + K * 2;
|
||||
buffer.push_back(d[0]);
|
||||
buffer.push_back(d[1]);
|
||||
}
|
||||
else
|
||||
buffer.push_back('0' + static_cast<char>(K));
|
||||
}
|
||||
|
||||
inline void Prettify(std::string &buffer, int length, int k) {
|
||||
const int kk = length + k; // 10^(kk-1) <= v < 10^kk
|
||||
|
||||
if (length <= kk && kk <= 21) {
|
||||
// 1234e7 -> 12340000000
|
||||
for (int i = length; i < kk; i++)
|
||||
buffer.push_back('0');
|
||||
}
|
||||
else if (0 < kk && kk <= 21) {
|
||||
// 1234e-2 -> 12.34
|
||||
buffer.insert(buffer.begin() + kk, '.');
|
||||
}
|
||||
else if (-6 < kk && kk <= 0) {
|
||||
// 1234e-6 -> 0.001234
|
||||
const int offset = 2 - kk;
|
||||
buffer.insert(buffer.begin(), '0');
|
||||
buffer.insert(buffer.begin() + 1, '.');
|
||||
for (int i = 2; i < offset; i++)
|
||||
buffer.insert(buffer.begin() + 2, '0');
|
||||
}
|
||||
else if (length == 1) {
|
||||
// 1e30
|
||||
buffer.push_back('e');
|
||||
WriteExponent(kk - 1, buffer);
|
||||
}
|
||||
else {
|
||||
// 1234e30 -> 1.234e33
|
||||
buffer.insert(buffer.begin() + 1, '.');
|
||||
buffer.push_back('e');
|
||||
WriteExponent(kk - 1, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
inline std::string dtoa_milo(double value) {
|
||||
std::string buffer;
|
||||
|
||||
if (std::isnan(value)) {
|
||||
return "nan";
|
||||
}
|
||||
if (std::isinf(value)) {
|
||||
if (value < 0) {
|
||||
return "-inf";
|
||||
} else {
|
||||
return "inf";
|
||||
}
|
||||
}
|
||||
|
||||
if (value == 0) {
|
||||
buffer = "0";
|
||||
}
|
||||
else {
|
||||
bool minus = false;
|
||||
if (value < 0) {
|
||||
minus = true;
|
||||
value = -value;
|
||||
}
|
||||
int length, K;
|
||||
Grisu2(value, buffer, &length, &K);
|
||||
Prettify(buffer, length, K);
|
||||
if (minus) {
|
||||
buffer.insert(buffer.begin(), '-');
|
||||
}
|
||||
}
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
}
|
269
mvt.cpp
269
mvt.cpp
@ -4,10 +4,15 @@
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <zlib.h>
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <ctype.h>
|
||||
#include "mvt.hpp"
|
||||
#include "geometry.hpp"
|
||||
#include "protozero/varint.hpp"
|
||||
#include "protozero/pbf_reader.hpp"
|
||||
#include "protozero/pbf_writer.hpp"
|
||||
#include "milo/dtoa_milo.h"
|
||||
|
||||
mvt_geometry::mvt_geometry(int nop, long long nx, long long ny) {
|
||||
this->op = nop;
|
||||
@ -29,25 +34,49 @@ int decompress(std::string const &input, std::string &output) {
|
||||
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);
|
||||
fprintf(stderr, "Decompression 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);
|
||||
inflate_s.next_out = (Bytef *) output.data();
|
||||
inflate_s.avail_out = output.size();
|
||||
|
||||
while (true) {
|
||||
size_t existing_output = inflate_s.next_out - (Bytef *) output.data();
|
||||
|
||||
output.resize(existing_output + 2 * inflate_s.avail_in + 100);
|
||||
inflate_s.next_out = (Bytef *) output.data() + existing_output;
|
||||
inflate_s.avail_out = output.size() - existing_output;
|
||||
|
||||
int ret = inflate(&inflate_s, 0);
|
||||
if (ret < 0) {
|
||||
fprintf(stderr, "Decompression error: ");
|
||||
if (ret == Z_DATA_ERROR) {
|
||||
fprintf(stderr, "data error");
|
||||
}
|
||||
if (ret == Z_STREAM_ERROR) {
|
||||
fprintf(stderr, "stream error");
|
||||
}
|
||||
if (ret == Z_MEM_ERROR) {
|
||||
fprintf(stderr, "out of memory");
|
||||
}
|
||||
if (ret == Z_BUF_ERROR) {
|
||||
fprintf(stderr, "no data in buffer");
|
||||
}
|
||||
fprintf(stderr, "\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
length += (2 * input.size() - inflate_s.avail_out);
|
||||
} while (inflate_s.avail_out == 0);
|
||||
if (ret == Z_STREAM_END) {
|
||||
break;
|
||||
}
|
||||
|
||||
// ret must be Z_OK or Z_NEED_DICT;
|
||||
// continue decompresing
|
||||
}
|
||||
|
||||
output.resize(inflate_s.next_out - (Bytef *) output.data());
|
||||
inflateEnd(&inflate_s);
|
||||
output.resize(length);
|
||||
return 1;
|
||||
}
|
||||
|
||||
@ -79,16 +108,20 @@ int compress(std::string const &input, std::string &output) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool mvt_tile::decode(std::string &message) {
|
||||
bool mvt_tile::decode(std::string &message, bool &was_compressed) {
|
||||
layers.clear();
|
||||
std::string src;
|
||||
|
||||
if (is_compressed(message)) {
|
||||
std::string uncompressed;
|
||||
decompress(message, uncompressed);
|
||||
if (decompress(message, uncompressed) == 0) {
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
src = uncompressed;
|
||||
was_compressed = true;
|
||||
} else {
|
||||
src = message;
|
||||
was_compressed = false;
|
||||
}
|
||||
|
||||
protozero::pbf_reader reader(src);
|
||||
@ -115,6 +148,9 @@ bool mvt_tile::decode(std::string &message) {
|
||||
protozero::pbf_reader value_reader(layer_reader.get_message());
|
||||
mvt_value value;
|
||||
|
||||
value.type = mvt_null;
|
||||
value.numeric_value.null_value = 0;
|
||||
|
||||
while (value_reader.next()) {
|
||||
switch (value_reader.tag()) {
|
||||
case 1: /* string */
|
||||
@ -296,6 +332,12 @@ std::string mvt_tile::encode() {
|
||||
value_writer.add_sint64(6, pbv.numeric_value.sint_value);
|
||||
} else if (pbv.type == mvt_bool) {
|
||||
value_writer.add_bool(7, pbv.numeric_value.bool_value);
|
||||
} else if (pbv.type == mvt_null) {
|
||||
fprintf(stderr, "Internal error: trying to write null attribute to tile\n");
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
fprintf(stderr, "Internal error: trying to write undefined attribute type to tile\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
layer_writer.add_message(4, value_string);
|
||||
@ -314,7 +356,7 @@ std::string mvt_tile::encode() {
|
||||
|
||||
std::vector<uint32_t> geometry;
|
||||
|
||||
int px = 0, py = 0;
|
||||
long long px = 0, py = 0;
|
||||
int cmd_idx = -1;
|
||||
int cmd = -1;
|
||||
int length = 0;
|
||||
@ -339,8 +381,13 @@ std::string mvt_tile::encode() {
|
||||
long long wwx = geom[g].x;
|
||||
long long wwy = geom[g].y;
|
||||
|
||||
int dx = wwx - px;
|
||||
int dy = wwy - py;
|
||||
long long dx = wwx - px;
|
||||
long long dy = wwy - py;
|
||||
|
||||
if (dx < INT_MIN || dx > INT_MAX || dy < INT_MIN || dy > INT_MAX) {
|
||||
fprintf(stderr, "Internal error: Geometry delta is too big: %lld,%lld\n", dx, dy);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
geometry.push_back(protozero::encode_zigzag32(dx));
|
||||
geometry.push_back(protozero::encode_zigzag32(dy));
|
||||
@ -367,10 +414,7 @@ std::string mvt_tile::encode() {
|
||||
writer.add_message(3, layer_string);
|
||||
}
|
||||
|
||||
std::string compressed;
|
||||
compress(data, compressed);
|
||||
|
||||
return compressed;
|
||||
return data;
|
||||
}
|
||||
|
||||
bool mvt_value::operator<(const mvt_value &o) const {
|
||||
@ -384,7 +428,8 @@ bool mvt_value::operator<(const mvt_value &o) const {
|
||||
(type == mvt_int && numeric_value.int_value < o.numeric_value.int_value) ||
|
||||
(type == mvt_uint && numeric_value.uint_value < o.numeric_value.uint_value) ||
|
||||
(type == mvt_sint && numeric_value.sint_value < o.numeric_value.sint_value) ||
|
||||
(type == mvt_bool && numeric_value.bool_value < o.numeric_value.bool_value)) {
|
||||
(type == mvt_bool && numeric_value.bool_value < o.numeric_value.bool_value) ||
|
||||
(type == mvt_null && numeric_value.null_value < o.numeric_value.null_value)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -392,6 +437,59 @@ bool mvt_value::operator<(const mvt_value &o) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
static std::string quote(std::string const &s) {
|
||||
std::string buf;
|
||||
|
||||
for (size_t i = 0; i < s.size(); i++) {
|
||||
unsigned char ch = s[i];
|
||||
|
||||
if (ch == '\\' || ch == '\"') {
|
||||
buf.push_back('\\');
|
||||
buf.push_back(ch);
|
||||
} else if (ch < ' ') {
|
||||
char tmp[7];
|
||||
sprintf(tmp, "\\u%04x", ch);
|
||||
buf.append(std::string(tmp));
|
||||
} else {
|
||||
buf.push_back(ch);
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
std::string mvt_value::toString() {
|
||||
if (type == mvt_string) {
|
||||
return quote(string_value);
|
||||
} else if (type == mvt_int) {
|
||||
return std::to_string(numeric_value.int_value);
|
||||
} else if (type == mvt_double) {
|
||||
double v = numeric_value.double_value;
|
||||
if (v == (long long) v) {
|
||||
return std::to_string((long long) v);
|
||||
} else {
|
||||
return milo::dtoa_milo(v);
|
||||
}
|
||||
} else if (type == mvt_float) {
|
||||
double v = numeric_value.float_value;
|
||||
if (v == (long long) v) {
|
||||
return std::to_string((long long) v);
|
||||
} else {
|
||||
return milo::dtoa_milo(v);
|
||||
}
|
||||
} else if (type == mvt_sint) {
|
||||
return std::to_string(numeric_value.sint_value);
|
||||
} else if (type == mvt_uint) {
|
||||
return std::to_string(numeric_value.uint_value);
|
||||
} else if (type == mvt_bool) {
|
||||
return numeric_value.bool_value ? "true" : "false";
|
||||
} else if (type == mvt_null) {
|
||||
return "null";
|
||||
} else {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void mvt_layer::tag(mvt_feature &feature, std::string key, mvt_value value) {
|
||||
size_t ko, vo;
|
||||
|
||||
@ -417,3 +515,130 @@ void mvt_layer::tag(mvt_feature &feature, std::string key, mvt_value value) {
|
||||
feature.tags.push_back(ko);
|
||||
feature.tags.push_back(vo);
|
||||
}
|
||||
|
||||
bool is_integer(const char *s, long long *v) {
|
||||
errno = 0;
|
||||
char *endptr;
|
||||
|
||||
*v = strtoll(s, &endptr, 0);
|
||||
if (*v == 0 && errno != 0) {
|
||||
return 0;
|
||||
}
|
||||
if ((*v == LLONG_MIN || *v == LLONG_MAX) && (errno == ERANGE || errno == EINVAL)) {
|
||||
return 0;
|
||||
}
|
||||
if (*endptr != '\0') {
|
||||
// Special case: If it is an integer followed by .0000 or similar,
|
||||
// it is still an integer
|
||||
|
||||
if (*endptr != '.') {
|
||||
return 0;
|
||||
}
|
||||
endptr++;
|
||||
for (; *endptr != '\0'; endptr++) {
|
||||
if (*endptr != '0') {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool is_unsigned_integer(const char *s, unsigned long long *v) {
|
||||
errno = 0;
|
||||
char *endptr;
|
||||
|
||||
// Special check because MacOS stroull() returns 1
|
||||
// for -18446744073709551615
|
||||
while (isspace(*s)) {
|
||||
s++;
|
||||
}
|
||||
if (*s == '-') {
|
||||
return 0;
|
||||
}
|
||||
|
||||
*v = strtoull(s, &endptr, 0);
|
||||
if (*v == 0 && errno != 0) {
|
||||
return 0;
|
||||
}
|
||||
if ((*v == ULLONG_MAX) && (errno == ERANGE || errno == EINVAL)) {
|
||||
return 0;
|
||||
}
|
||||
if (*endptr != '\0') {
|
||||
// Special case: If it is an integer followed by .0000 or similar,
|
||||
// it is still an integer
|
||||
|
||||
if (*endptr != '.') {
|
||||
return 0;
|
||||
}
|
||||
endptr++;
|
||||
for (; *endptr != '\0'; endptr++) {
|
||||
if (*endptr != '0') {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
mvt_value stringified_to_mvt_value(int type, const char *s) {
|
||||
mvt_value tv;
|
||||
|
||||
if (type == mvt_double) {
|
||||
long long v;
|
||||
unsigned long long uv;
|
||||
if (is_unsigned_integer(s, &uv)) {
|
||||
if (uv <= LLONG_MAX) {
|
||||
tv.type = mvt_int;
|
||||
tv.numeric_value.int_value = uv;
|
||||
} else {
|
||||
tv.type = mvt_uint;
|
||||
tv.numeric_value.uint_value = uv;
|
||||
}
|
||||
} else if (is_integer(s, &v)) {
|
||||
tv.type = mvt_sint;
|
||||
tv.numeric_value.sint_value = v;
|
||||
} else {
|
||||
errno = 0;
|
||||
char *endptr;
|
||||
|
||||
float f = strtof(s, &endptr);
|
||||
|
||||
if (endptr == s || ((f == HUGE_VAL || f == HUGE_VALF || f == HUGE_VALL) && errno == ERANGE)) {
|
||||
double d = strtod(s, &endptr);
|
||||
if (endptr == s || ((d == HUGE_VAL || d == HUGE_VALF || d == HUGE_VALL) && errno == ERANGE)) {
|
||||
fprintf(stderr, "Warning: numeric value %s could not be represented\n", s);
|
||||
}
|
||||
tv.type = mvt_double;
|
||||
tv.numeric_value.double_value = d;
|
||||
} else {
|
||||
double d = atof(s);
|
||||
if (f == d) {
|
||||
tv.type = mvt_float;
|
||||
tv.numeric_value.float_value = f;
|
||||
} else {
|
||||
// Conversion succeeded, but lost precision, so use double
|
||||
tv.type = mvt_double;
|
||||
tv.numeric_value.double_value = d;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type == mvt_bool) {
|
||||
tv.type = mvt_bool;
|
||||
tv.numeric_value.bool_value = (s[0] == 't');
|
||||
} else if (type == mvt_null) {
|
||||
tv.type = mvt_null;
|
||||
tv.numeric_value.null_value = 0;
|
||||
} else {
|
||||
tv.type = mvt_string;
|
||||
tv.string_value = s;
|
||||
}
|
||||
|
||||
return tv;
|
||||
}
|
||||
|
75
mvt.hpp
75
mvt.hpp
@ -1,3 +1,12 @@
|
||||
#ifndef MVT_HPP
|
||||
#define MVT_HPP
|
||||
|
||||
#include <sqlite3.h>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
struct mvt_value;
|
||||
struct mvt_layer;
|
||||
|
||||
@ -8,11 +17,23 @@ enum mvt_operation {
|
||||
};
|
||||
|
||||
struct mvt_geometry {
|
||||
int x;
|
||||
int y;
|
||||
int /* mvt_operation */ op;
|
||||
long long x = 0;
|
||||
long long y = 0;
|
||||
int /* mvt_operation */ op = 0;
|
||||
|
||||
mvt_geometry(int op, long long x, long long y);
|
||||
|
||||
bool operator<(mvt_geometry const &s) const {
|
||||
if (y < s.y || (y == s.y && x < s.x)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(mvt_geometry const &s) const {
|
||||
return y == s.y && x == s.x;
|
||||
}
|
||||
};
|
||||
|
||||
enum mvt_geometry_type {
|
||||
@ -22,11 +43,12 @@ enum mvt_geometry_type {
|
||||
};
|
||||
|
||||
struct mvt_feature {
|
||||
std::vector<unsigned> tags;
|
||||
std::vector<mvt_geometry> geometry;
|
||||
int /* mvt_geometry_type */ type;
|
||||
unsigned long long id;
|
||||
bool has_id;
|
||||
std::vector<unsigned> tags{};
|
||||
std::vector<mvt_geometry> geometry{};
|
||||
int /* mvt_geometry_type */ type = 0;
|
||||
unsigned long long id = 0;
|
||||
bool has_id = false;
|
||||
bool dropped = false;
|
||||
|
||||
mvt_feature() {
|
||||
has_id = false;
|
||||
@ -41,7 +63,8 @@ enum mvt_value_type {
|
||||
mvt_int,
|
||||
mvt_uint,
|
||||
mvt_sint,
|
||||
mvt_bool
|
||||
mvt_bool,
|
||||
mvt_null,
|
||||
};
|
||||
|
||||
struct mvt_value {
|
||||
@ -54,35 +77,49 @@ struct mvt_value {
|
||||
unsigned long long uint_value;
|
||||
long long sint_value;
|
||||
bool bool_value;
|
||||
int null_value;
|
||||
} numeric_value;
|
||||
|
||||
bool operator<(const mvt_value &o) const;
|
||||
std::string toString();
|
||||
|
||||
mvt_value() {
|
||||
this->type = mvt_double;
|
||||
this->string_value = "";
|
||||
this->numeric_value.double_value = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct mvt_layer {
|
||||
int version;
|
||||
std::string name;
|
||||
std::vector<mvt_feature> features;
|
||||
std::vector<std::string> keys;
|
||||
std::vector<mvt_value> values;
|
||||
int extent;
|
||||
int version = 0;
|
||||
std::string name = "";
|
||||
std::vector<mvt_feature> features{};
|
||||
std::vector<std::string> keys{};
|
||||
std::vector<mvt_value> values{};
|
||||
long long extent = 0;
|
||||
|
||||
// Add a key-value pair to a feature, using this layer's constant pool
|
||||
void tag(mvt_feature &feature, std::string key, mvt_value value);
|
||||
|
||||
// For tracking the key-value constants already used in this layer
|
||||
std::map<std::string, size_t> key_map;
|
||||
std::map<mvt_value, size_t> value_map;
|
||||
std::map<std::string, size_t> key_map{};
|
||||
std::map<mvt_value, size_t> value_map{};
|
||||
};
|
||||
|
||||
struct mvt_tile {
|
||||
std::vector<mvt_layer> layers;
|
||||
std::vector<mvt_layer> layers{};
|
||||
|
||||
std::string encode();
|
||||
bool decode(std::string &message);
|
||||
bool decode(std::string &message, bool &was_compressed);
|
||||
};
|
||||
|
||||
bool is_compressed(std::string const &data);
|
||||
int decompress(std::string const &input, std::string &output);
|
||||
int compress(std::string const &input, std::string &output);
|
||||
int dezig(unsigned n);
|
||||
|
||||
mvt_value stringified_to_mvt_value(int type, const char *s);
|
||||
|
||||
bool is_integer(const char *s, long long *v);
|
||||
bool is_unsigned_integer(const char *s, unsigned long long *v);
|
||||
#endif
|
||||
|
22
options.hpp
22
options.hpp
@ -1,8 +1,11 @@
|
||||
#ifndef OPTIONS_HPP
|
||||
#define OPTIONS_HPP
|
||||
|
||||
#define A_COALESCE ((int) 'c')
|
||||
#define A_REVERSE ((int) 'r')
|
||||
#define A_REORDER ((int) 'o')
|
||||
#define A_LINE_DROP ((int) 'l')
|
||||
#define A_DEBUG_POLYGON ((int) 'D')
|
||||
#define A_DEBUG_POLYGON ((int) '@')
|
||||
#define A_POLYGON_DROP ((int) 'p')
|
||||
#define A_DETECT_SHARED_BORDERS ((int) 'b')
|
||||
#define A_PREFER_RADIX_SORT ((int) 'R')
|
||||
@ -12,10 +15,20 @@
|
||||
#define A_DROP_DENSEST_AS_NEEDED ((int) 's')
|
||||
#define A_DROP_FRACTION_AS_NEEDED ((int) 'd')
|
||||
#define A_DROP_SMALLEST_AS_NEEDED ((int) 'n')
|
||||
#define A_COALESCE_DENSEST_AS_NEEDED ((int) 'S')
|
||||
#define A_COALESCE_SMALLEST_AS_NEEDED ((int) 'N')
|
||||
#define A_COALESCE_FRACTION_AS_NEEDED ((int) 'D')
|
||||
#define A_GRID_LOW_ZOOMS ((int) 'L')
|
||||
#define A_DETECT_WRAPAROUND ((int) 'w')
|
||||
#define A_EXTEND_ZOOMS ((int) 'e')
|
||||
#define A_CLUSTER_DENSEST_AS_NEEDED ((int) 'C')
|
||||
#define A_GENERATE_IDS ((int) 'i')
|
||||
#define A_CONVERT_NUMERIC_IDS ((int) 'I')
|
||||
#define A_HILBERT ((int) 'h')
|
||||
|
||||
#define P_SIMPLIFY ((int) 's')
|
||||
#define P_SIMPLIFY_LOW ((int) 'S')
|
||||
#define P_SIMPLIFY_SHARED_NODES ((int) 'n')
|
||||
#define P_FEATURE_LIMIT ((int) 'f')
|
||||
#define P_KILOBYTE_LIMIT ((int) 'k')
|
||||
#define P_DYNAMIC_DROP ((int) 'd')
|
||||
@ -24,6 +37,13 @@
|
||||
#define P_CLIPPING ((int) 'c')
|
||||
#define P_DUPLICATION ((int) 'D')
|
||||
#define P_TINY_POLYGON_REDUCTION ((int) 't')
|
||||
#define P_TILE_COMPRESSION ((int) 'C')
|
||||
#define P_TILE_STATS ((int) 'g')
|
||||
#define P_USE_SOURCE_POLYGON_WINDING ((int) 'w')
|
||||
#define P_REVERSE_SOURCE_POLYGON_WINDING ((int) 'W')
|
||||
#define P_EMPTY_CSV_COLUMNS ((int) 'e')
|
||||
|
||||
extern int prevent[256];
|
||||
extern int additional[256];
|
||||
|
||||
#endif
|
||||
|
660
plugin.cpp
Normal file
660
plugin.cpp
Normal file
@ -0,0 +1,660 @@
|
||||
#ifdef __APPLE__
|
||||
#define _DARWIN_UNLIMITED_STREAMS
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include <cmath>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
#include <sqlite3.h>
|
||||
#include <limits.h>
|
||||
#include "main.hpp"
|
||||
#include "mvt.hpp"
|
||||
#include "mbtiles.hpp"
|
||||
#include "projection.hpp"
|
||||
#include "geometry.hpp"
|
||||
#include "serial.hpp"
|
||||
|
||||
extern "C" {
|
||||
#include "jsonpull/jsonpull.h"
|
||||
}
|
||||
|
||||
#include "plugin.hpp"
|
||||
#include "write_json.hpp"
|
||||
#include "read_json.hpp"
|
||||
|
||||
struct writer_arg {
|
||||
int write_to;
|
||||
std::vector<mvt_layer> *layers;
|
||||
unsigned z;
|
||||
unsigned x;
|
||||
unsigned y;
|
||||
int extent;
|
||||
};
|
||||
|
||||
void *run_writer(void *a) {
|
||||
writer_arg *wa = (writer_arg *) a;
|
||||
|
||||
FILE *fp = fdopen(wa->write_to, "w");
|
||||
if (fp == NULL) {
|
||||
perror("fdopen (pipe writer)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
json_writer state(fp);
|
||||
for (size_t i = 0; i < wa->layers->size(); i++) {
|
||||
layer_to_geojson((*(wa->layers))[i], wa->z, wa->x, wa->y, false, true, false, true, 0, 0, 0, true, state);
|
||||
}
|
||||
|
||||
if (fclose(fp) != 0) {
|
||||
if (errno == EPIPE) {
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
fprintf(stderr, "Warning: broken pipe in postfilter\n");
|
||||
warned = true;
|
||||
}
|
||||
} else {
|
||||
perror("fclose output to filter");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// XXX deduplicate
|
||||
static std::vector<mvt_geometry> to_feature(drawvec &geom) {
|
||||
std::vector<mvt_geometry> out;
|
||||
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
out.push_back(mvt_geometry(geom[i].op, geom[i].x, geom[i].y));
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// Reads from the postfilter
|
||||
std::vector<mvt_layer> parse_layers(int fd, int z, unsigned x, unsigned y, std::vector<std::map<std::string, layermap_entry>> *layermaps, size_t tiling_seg, std::vector<std::vector<std::string>> *layer_unmaps, int extent) {
|
||||
std::map<std::string, mvt_layer> ret;
|
||||
|
||||
FILE *f = fdopen(fd, "r");
|
||||
if (f == NULL) {
|
||||
perror("fdopen filter output");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
json_pull *jp = json_begin_file(f);
|
||||
|
||||
while (1) {
|
||||
json_object *j = json_read(jp);
|
||||
if (j == NULL) {
|
||||
if (jp->error != NULL) {
|
||||
fprintf(stderr, "Filter output:%d: %s\n", jp->line, jp->error);
|
||||
if (jp->root != NULL) {
|
||||
json_context(jp->root);
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
json_free(jp->root);
|
||||
break;
|
||||
}
|
||||
|
||||
json_object *type = json_hash_get(j, "type");
|
||||
if (type == NULL || type->type != JSON_STRING) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(type->string, "Feature") != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
json_object *geometry = json_hash_get(j, "geometry");
|
||||
if (geometry == NULL) {
|
||||
fprintf(stderr, "Filter output:%d: filtered feature with no geometry\n", jp->line);
|
||||
json_context(j);
|
||||
json_free(j);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
json_object *properties = json_hash_get(j, "properties");
|
||||
if (properties == NULL || (properties->type != JSON_HASH && properties->type != JSON_NULL)) {
|
||||
fprintf(stderr, "Filter output:%d: feature without properties hash\n", jp->line);
|
||||
json_context(j);
|
||||
json_free(j);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
json_object *geometry_type = json_hash_get(geometry, "type");
|
||||
if (geometry_type == NULL) {
|
||||
fprintf(stderr, "Filter output:%d: null geometry (additional not reported)\n", jp->line);
|
||||
json_context(j);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (geometry_type->type != JSON_STRING) {
|
||||
fprintf(stderr, "Filter output:%d: geometry type is not a string\n", jp->line);
|
||||
json_context(j);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
json_object *coordinates = json_hash_get(geometry, "coordinates");
|
||||
if (coordinates == NULL || coordinates->type != JSON_ARRAY) {
|
||||
fprintf(stderr, "Filter output:%d: feature without coordinates array\n", jp->line);
|
||||
json_context(j);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int t;
|
||||
for (t = 0; t < GEOM_TYPES; t++) {
|
||||
if (strcmp(geometry_type->string, geometry_names[t]) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (t >= GEOM_TYPES) {
|
||||
fprintf(stderr, "Filter output:%d: Can't handle geometry type %s\n", jp->line, geometry_type->string);
|
||||
json_context(j);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::string layername = "unknown";
|
||||
json_object *tippecanoe = json_hash_get(j, "tippecanoe");
|
||||
json_object *layer = NULL;
|
||||
if (tippecanoe != NULL) {
|
||||
layer = json_hash_get(tippecanoe, "layer");
|
||||
if (layer != NULL && layer->type == JSON_STRING) {
|
||||
layername = std::string(layer->string);
|
||||
}
|
||||
}
|
||||
|
||||
if (ret.count(layername) == 0) {
|
||||
mvt_layer l;
|
||||
l.name = layername;
|
||||
l.version = 2;
|
||||
l.extent = extent;
|
||||
|
||||
ret.insert(std::pair<std::string, mvt_layer>(layername, l));
|
||||
}
|
||||
auto l = ret.find(layername);
|
||||
|
||||
drawvec dv;
|
||||
parse_geometry(t, coordinates, dv, VT_MOVETO, "Filter output", jp->line, j);
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
dv = fix_polygon(dv);
|
||||
}
|
||||
|
||||
// Scale and offset geometry from global to tile
|
||||
for (size_t i = 0; i < dv.size(); i++) {
|
||||
long long scale = 1LL << (32 - z);
|
||||
dv[i].x = std::round((dv[i].x - scale * x) * extent / (double) scale);
|
||||
dv[i].y = std::round((dv[i].y - scale * y) * extent / (double) scale);
|
||||
}
|
||||
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
dv = clean_or_clip_poly(dv, 0, 0, false);
|
||||
if (dv.size() < 3) {
|
||||
dv.clear();
|
||||
}
|
||||
}
|
||||
dv = remove_noop(dv, mb_geometry[t], 0);
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
dv = close_poly(dv);
|
||||
}
|
||||
|
||||
if (dv.size() > 0) {
|
||||
mvt_feature feature;
|
||||
feature.type = mb_geometry[t];
|
||||
feature.geometry = to_feature(dv);
|
||||
|
||||
json_object *id = json_hash_get(j, "id");
|
||||
if (id != NULL) {
|
||||
feature.id = atoll(id->string);
|
||||
feature.has_id = true;
|
||||
}
|
||||
|
||||
std::map<std::string, layermap_entry> &layermap = (*layermaps)[tiling_seg];
|
||||
if (layermap.count(layername) == 0) {
|
||||
layermap_entry lme = layermap_entry(layermap.size());
|
||||
lme.minzoom = z;
|
||||
lme.maxzoom = z;
|
||||
|
||||
layermap.insert(std::pair<std::string, layermap_entry>(layername, lme));
|
||||
|
||||
if (lme.id >= (*layer_unmaps)[tiling_seg].size()) {
|
||||
(*layer_unmaps)[tiling_seg].resize(lme.id + 1);
|
||||
(*layer_unmaps)[tiling_seg][lme.id] = layername;
|
||||
}
|
||||
}
|
||||
|
||||
auto fk = layermap.find(layername);
|
||||
if (fk == layermap.end()) {
|
||||
fprintf(stderr, "Internal error: layer %s not found\n", layername.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (z < fk->second.minzoom) {
|
||||
fk->second.minzoom = z;
|
||||
}
|
||||
if (z > fk->second.maxzoom) {
|
||||
fk->second.maxzoom = z;
|
||||
}
|
||||
|
||||
if (feature.type == mvt_point) {
|
||||
fk->second.points++;
|
||||
} else if (feature.type == mvt_linestring) {
|
||||
fk->second.lines++;
|
||||
} else if (feature.type == mvt_polygon) {
|
||||
fk->second.polygons++;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < properties->length; i++) {
|
||||
int tp = -1;
|
||||
std::string s;
|
||||
|
||||
stringify_value(properties->values[i], tp, s, "Filter output", jp->line, j);
|
||||
|
||||
// Nulls can be excluded here because this is the postfilter
|
||||
// and it is nearly time to create the vector representation
|
||||
|
||||
if (tp >= 0 && tp != mvt_null) {
|
||||
mvt_value v = stringified_to_mvt_value(tp, s.c_str());
|
||||
l->second.tag(feature, std::string(properties->keys[i]->string), v);
|
||||
|
||||
type_and_string attrib;
|
||||
attrib.type = tp;
|
||||
attrib.string = s;
|
||||
|
||||
add_to_file_keys(fk->second.file_keys, std::string(properties->keys[i]->string), attrib);
|
||||
}
|
||||
}
|
||||
|
||||
l->second.features.push_back(feature);
|
||||
}
|
||||
|
||||
json_free(j);
|
||||
}
|
||||
|
||||
json_end(jp);
|
||||
if (fclose(f) != 0) {
|
||||
perror("fclose postfilter output");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::vector<mvt_layer> final;
|
||||
for (auto a : ret) {
|
||||
final.push_back(a.second);
|
||||
}
|
||||
return final;
|
||||
}
|
||||
|
||||
// Reads from the prefilter
|
||||
serial_feature parse_feature(json_pull *jp, int z, unsigned x, unsigned y, std::vector<std::map<std::string, layermap_entry>> *layermaps, size_t tiling_seg, std::vector<std::vector<std::string>> *layer_unmaps, bool postfilter) {
|
||||
serial_feature sf;
|
||||
|
||||
while (1) {
|
||||
json_object *j = json_read(jp);
|
||||
if (j == NULL) {
|
||||
if (jp->error != NULL) {
|
||||
fprintf(stderr, "Filter output:%d: %s\n", jp->line, jp->error);
|
||||
if (jp->root != NULL) {
|
||||
json_context(jp->root);
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
json_free(jp->root);
|
||||
sf.t = -1;
|
||||
return sf;
|
||||
}
|
||||
|
||||
json_object *type = json_hash_get(j, "type");
|
||||
if (type == NULL || type->type != JSON_STRING) {
|
||||
continue;
|
||||
}
|
||||
if (strcmp(type->string, "Feature") != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
json_object *geometry = json_hash_get(j, "geometry");
|
||||
if (geometry == NULL) {
|
||||
fprintf(stderr, "Filter output:%d: filtered feature with no geometry\n", jp->line);
|
||||
json_context(j);
|
||||
json_free(j);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
json_object *properties = json_hash_get(j, "properties");
|
||||
if (properties == NULL || (properties->type != JSON_HASH && properties->type != JSON_NULL)) {
|
||||
fprintf(stderr, "Filter output:%d: feature without properties hash\n", jp->line);
|
||||
json_context(j);
|
||||
json_free(j);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
json_object *geometry_type = json_hash_get(geometry, "type");
|
||||
if (geometry_type == NULL) {
|
||||
fprintf(stderr, "Filter output:%d: null geometry (additional not reported)\n", jp->line);
|
||||
json_context(j);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (geometry_type->type != JSON_STRING) {
|
||||
fprintf(stderr, "Filter output:%d: geometry type is not a string\n", jp->line);
|
||||
json_context(j);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
json_object *coordinates = json_hash_get(geometry, "coordinates");
|
||||
if (coordinates == NULL || coordinates->type != JSON_ARRAY) {
|
||||
fprintf(stderr, "Filter output:%d: feature without coordinates array\n", jp->line);
|
||||
json_context(j);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int t;
|
||||
for (t = 0; t < GEOM_TYPES; t++) {
|
||||
if (strcmp(geometry_type->string, geometry_names[t]) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (t >= GEOM_TYPES) {
|
||||
fprintf(stderr, "Filter output:%d: Can't handle geometry type %s\n", jp->line, geometry_type->string);
|
||||
json_context(j);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
drawvec dv;
|
||||
parse_geometry(t, coordinates, dv, VT_MOVETO, "Filter output", jp->line, j);
|
||||
if (mb_geometry[t] == VT_POLYGON) {
|
||||
dv = fix_polygon(dv);
|
||||
}
|
||||
|
||||
// Scale and offset geometry from global to tile
|
||||
double scale = 1LL << geometry_scale;
|
||||
for (size_t i = 0; i < dv.size(); i++) {
|
||||
unsigned sx = 0, sy = 0;
|
||||
if (z != 0) {
|
||||
sx = x << (32 - z);
|
||||
sy = y << (32 - z);
|
||||
}
|
||||
dv[i].x = std::round(dv[i].x / scale) * scale - sx;
|
||||
dv[i].y = std::round(dv[i].y / scale) * scale - sy;
|
||||
}
|
||||
|
||||
if (dv.size() > 0) {
|
||||
sf.t = mb_geometry[t];
|
||||
sf.segment = tiling_seg;
|
||||
sf.geometry = dv;
|
||||
sf.seq = 0;
|
||||
sf.index = 0;
|
||||
sf.bbox[0] = sf.bbox[1] = LLONG_MAX;
|
||||
sf.bbox[2] = sf.bbox[3] = LLONG_MIN;
|
||||
sf.extent = 0;
|
||||
sf.metapos = 0;
|
||||
sf.has_id = false;
|
||||
|
||||
std::string layername = "unknown";
|
||||
json_object *tippecanoe = json_hash_get(j, "tippecanoe");
|
||||
if (tippecanoe != NULL) {
|
||||
json_object *layer = json_hash_get(tippecanoe, "layer");
|
||||
if (layer != NULL && layer->type == JSON_STRING) {
|
||||
layername = std::string(layer->string);
|
||||
}
|
||||
|
||||
json_object *index = json_hash_get(tippecanoe, "index");
|
||||
if (index != NULL && index->type == JSON_NUMBER) {
|
||||
sf.index = index->number;
|
||||
}
|
||||
|
||||
json_object *sequence = json_hash_get(tippecanoe, "sequence");
|
||||
if (sequence != NULL && sequence->type == JSON_NUMBER) {
|
||||
sf.seq = sequence->number;
|
||||
}
|
||||
|
||||
json_object *extent = json_hash_get(tippecanoe, "extent");
|
||||
if (extent != NULL && extent->type == JSON_NUMBER) {
|
||||
sf.extent = extent->number;
|
||||
}
|
||||
|
||||
json_object *dropped = json_hash_get(tippecanoe, "dropped");
|
||||
if (dropped != NULL && dropped->type == JSON_TRUE) {
|
||||
sf.dropped = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < dv.size(); i++) {
|
||||
if (dv[i].op == VT_MOVETO || dv[i].op == VT_LINETO) {
|
||||
if (dv[i].x < sf.bbox[0]) {
|
||||
sf.bbox[0] = dv[i].x;
|
||||
}
|
||||
if (dv[i].y < sf.bbox[1]) {
|
||||
sf.bbox[1] = dv[i].y;
|
||||
}
|
||||
if (dv[i].x > sf.bbox[2]) {
|
||||
sf.bbox[2] = dv[i].x;
|
||||
}
|
||||
if (dv[i].y > sf.bbox[3]) {
|
||||
sf.bbox[3] = dv[i].y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json_object *id = json_hash_get(j, "id");
|
||||
if (id != NULL) {
|
||||
sf.id = atoll(id->string);
|
||||
sf.has_id = true;
|
||||
}
|
||||
|
||||
std::map<std::string, layermap_entry> &layermap = (*layermaps)[tiling_seg];
|
||||
|
||||
if (layermap.count(layername) == 0) {
|
||||
layermap_entry lme = layermap_entry(layermap.size());
|
||||
lme.minzoom = z;
|
||||
lme.maxzoom = z;
|
||||
|
||||
layermap.insert(std::pair<std::string, layermap_entry>(layername, lme));
|
||||
|
||||
if (lme.id >= (*layer_unmaps)[tiling_seg].size()) {
|
||||
(*layer_unmaps)[tiling_seg].resize(lme.id + 1);
|
||||
(*layer_unmaps)[tiling_seg][lme.id] = layername;
|
||||
}
|
||||
}
|
||||
|
||||
auto fk = layermap.find(layername);
|
||||
if (fk == layermap.end()) {
|
||||
fprintf(stderr, "Internal error: layer %s not found\n", layername.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
sf.layer = fk->second.id;
|
||||
|
||||
if (z < fk->second.minzoom) {
|
||||
fk->second.minzoom = z;
|
||||
}
|
||||
if (z > fk->second.maxzoom) {
|
||||
fk->second.maxzoom = z;
|
||||
}
|
||||
|
||||
if (!postfilter) {
|
||||
if (sf.t == mvt_point) {
|
||||
fk->second.points++;
|
||||
} else if (sf.t == mvt_linestring) {
|
||||
fk->second.lines++;
|
||||
} else if (sf.t == mvt_polygon) {
|
||||
fk->second.polygons++;
|
||||
}
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < properties->length; i++) {
|
||||
serial_val v;
|
||||
v.type = -1;
|
||||
|
||||
stringify_value(properties->values[i], v.type, v.s, "Filter output", jp->line, j);
|
||||
|
||||
// Nulls can be excluded here because the expression evaluation filter
|
||||
// would have already run before prefiltering
|
||||
|
||||
if (v.type >= 0 && v.type != mvt_null) {
|
||||
sf.full_keys.push_back(std::string(properties->keys[i]->string));
|
||||
sf.full_values.push_back(v);
|
||||
|
||||
type_and_string attrib;
|
||||
attrib.string = v.s;
|
||||
attrib.type = v.type;
|
||||
|
||||
if (!postfilter) {
|
||||
add_to_file_keys(fk->second.file_keys, std::string(properties->keys[i]->string), attrib);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
json_free(j);
|
||||
return sf;
|
||||
}
|
||||
|
||||
json_free(j);
|
||||
}
|
||||
}
|
||||
|
||||
static pthread_mutex_t pipe_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||
|
||||
void setup_filter(const char *filter, int *write_to, int *read_from, pid_t *pid, unsigned z, unsigned x, unsigned y) {
|
||||
// This will create two pipes, a new thread, and a new process.
|
||||
//
|
||||
// The new process will read from one pipe and write to the other, and execute the filter.
|
||||
// The new thread will write the GeoJSON to the pipe that leads to the filter.
|
||||
// The original thread will read the GeoJSON from the filter and convert it back into vector tiles.
|
||||
|
||||
if (pthread_mutex_lock(&pipe_lock) != 0) {
|
||||
perror("pthread_mutex_lock (pipe)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
int pipe_orig[2], pipe_filtered[2];
|
||||
if (pipe(pipe_orig) < 0) {
|
||||
perror("pipe (original features)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (pipe(pipe_filtered) < 0) {
|
||||
perror("pipe (filtered features)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::string z_str = std::to_string(z);
|
||||
std::string x_str = std::to_string(x);
|
||||
std::string y_str = std::to_string(y);
|
||||
|
||||
*pid = fork();
|
||||
if (*pid < 0) {
|
||||
perror("fork");
|
||||
exit(EXIT_FAILURE);
|
||||
} else if (*pid == 0) {
|
||||
// child
|
||||
|
||||
if (dup2(pipe_orig[0], 0) < 0) {
|
||||
perror("dup child stdin");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (dup2(pipe_filtered[1], 1) < 0) {
|
||||
perror("dup child stdout");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (close(pipe_orig[1]) != 0) {
|
||||
perror("close output to filter");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (close(pipe_filtered[0]) != 0) {
|
||||
perror("close input from filter");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (close(pipe_orig[0]) != 0) {
|
||||
perror("close dup input of filter");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (close(pipe_filtered[1]) != 0) {
|
||||
perror("close dup output of filter");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// XXX close other fds?
|
||||
|
||||
if (execlp("sh", "sh", "-c", filter, "sh", z_str.c_str(), x_str.c_str(), y_str.c_str(), NULL) != 0) {
|
||||
perror("exec");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
// parent
|
||||
|
||||
if (close(pipe_orig[0]) != 0) {
|
||||
perror("close filter-side reader");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (close(pipe_filtered[1]) != 0) {
|
||||
perror("close filter-side writer");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (fcntl(pipe_orig[1], F_SETFD, FD_CLOEXEC) != 0) {
|
||||
perror("cloxec output to filter");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (fcntl(pipe_filtered[0], F_SETFD, FD_CLOEXEC) != 0) {
|
||||
perror("cloxec input from filter");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (pthread_mutex_unlock(&pipe_lock) != 0) {
|
||||
perror("pthread_mutex_unlock (pipe_lock)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
*write_to = pipe_orig[1];
|
||||
*read_from = pipe_filtered[0];
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<mvt_layer> filter_layers(const char *filter, std::vector<mvt_layer> &layers, unsigned z, unsigned x, unsigned y, std::vector<std::map<std::string, layermap_entry>> *layermaps, size_t tiling_seg, std::vector<std::vector<std::string>> *layer_unmaps, int extent) {
|
||||
int write_to, read_from;
|
||||
pid_t pid;
|
||||
setup_filter(filter, &write_to, &read_from, &pid, z, x, y);
|
||||
|
||||
writer_arg wa;
|
||||
wa.write_to = write_to;
|
||||
wa.layers = &layers;
|
||||
wa.z = z;
|
||||
wa.x = x;
|
||||
wa.y = y;
|
||||
wa.extent = extent;
|
||||
|
||||
pthread_t writer;
|
||||
if (pthread_create(&writer, NULL, run_writer, &wa) != 0) {
|
||||
perror("pthread_create (filter writer)");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
std::vector<mvt_layer> nlayers = parse_layers(read_from, z, x, y, layermaps, tiling_seg, layer_unmaps, extent);
|
||||
|
||||
while (1) {
|
||||
int stat_loc;
|
||||
if (waitpid(pid, &stat_loc, 0) < 0) {
|
||||
perror("waitpid for filter\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (WIFEXITED(stat_loc) || WIFSIGNALED(stat_loc)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void *ret;
|
||||
if (pthread_join(writer, &ret) != 0) {
|
||||
perror("pthread_join filter writer");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return nlayers;
|
||||
}
|
3
plugin.hpp
Normal file
3
plugin.hpp
Normal file
@ -0,0 +1,3 @@
|
||||
std::vector<mvt_layer> filter_layers(const char *filter, std::vector<mvt_layer> &layer, unsigned z, unsigned x, unsigned y, std::vector<std::map<std::string, layermap_entry>> *layermaps, size_t tiling_seg, std::vector<std::vector<std::string>> *layer_unmaps, int extent);
|
||||
void setup_filter(const char *filter, int *write_to, int *read_from, pid_t *pid, unsigned z, unsigned x, unsigned y);
|
||||
serial_feature parse_feature(json_pull *jp, int z, unsigned x, unsigned y, std::vector<std::map<std::string, layermap_entry>> *layermaps, size_t tiling_seg, std::vector<std::vector<std::string>> *layer_unmaps, bool filters);
|
85
pool.cpp
85
pool.cpp
@ -2,47 +2,43 @@
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include "memfile.hpp"
|
||||
#include "pool.hpp"
|
||||
|
||||
static unsigned char swizzle[256] = {
|
||||
0x00, 0xBF, 0x18, 0xDE, 0x93, 0xC9, 0xB1, 0x5E, 0xDF, 0xBE, 0x72, 0x5A, 0xBB, 0x42, 0x64, 0xC6,
|
||||
0xD8, 0xB7, 0x15, 0x74, 0x1C, 0x8B, 0x91, 0xF5, 0x29, 0x46, 0xEC, 0x6F, 0xCA, 0x20, 0xF0, 0x06,
|
||||
0x27, 0x61, 0x87, 0xE0, 0x6E, 0x43, 0x50, 0xC5, 0x1B, 0xB4, 0x37, 0xC3, 0x69, 0xA6, 0xEE, 0x80,
|
||||
0xAF, 0x9B, 0xA1, 0x76, 0x23, 0x24, 0x53, 0xF3, 0x5B, 0x65, 0x19, 0xF4, 0xFC, 0xDD, 0x26, 0xE8,
|
||||
0x10, 0xF7, 0xCE, 0x92, 0x48, 0xF6, 0x94, 0x60, 0x07, 0xC4, 0xB9, 0x97, 0x6D, 0xA4, 0x11, 0x0D,
|
||||
0x1F, 0x4D, 0x13, 0xB0, 0x5D, 0xBA, 0x31, 0xD5, 0x8D, 0x51, 0x36, 0x96, 0x7A, 0x03, 0x7F, 0xDA,
|
||||
0x17, 0xDB, 0xD4, 0x83, 0xE2, 0x79, 0x6A, 0xE1, 0x95, 0x38, 0xFF, 0x28, 0xB2, 0xB3, 0xA7, 0xAE,
|
||||
0xF8, 0x54, 0xCC, 0xDC, 0x9A, 0x6B, 0xFB, 0x3F, 0xD7, 0xBC, 0x21, 0xC8, 0x71, 0x09, 0x16, 0xAC,
|
||||
0x3C, 0x8A, 0x62, 0x05, 0xC2, 0x8C, 0x32, 0x4E, 0x35, 0x9C, 0x5F, 0x75, 0xCD, 0x2E, 0xA2, 0x3E,
|
||||
0x1A, 0xC1, 0x8E, 0x14, 0xA0, 0xD3, 0x7D, 0xD9, 0xEB, 0x5C, 0x70, 0xE6, 0x9E, 0x12, 0x3B, 0xEF,
|
||||
0x1E, 0x49, 0xD2, 0x98, 0x39, 0x7E, 0x44, 0x4B, 0x6C, 0x88, 0x02, 0x2C, 0xAD, 0xE5, 0x9F, 0x40,
|
||||
0x7B, 0x4A, 0x3D, 0xA9, 0xAB, 0x0B, 0xD6, 0x2F, 0x90, 0x2A, 0xB6, 0x1D, 0xC7, 0x22, 0x55, 0x34,
|
||||
0x0A, 0xD0, 0xB5, 0x68, 0xE3, 0x59, 0xFD, 0xFA, 0x57, 0x77, 0x25, 0xA3, 0x04, 0xB8, 0x33, 0x89,
|
||||
0x78, 0x82, 0xE4, 0xC0, 0x0E, 0x8F, 0x85, 0xD1, 0x84, 0x08, 0x67, 0x47, 0x9D, 0xCB, 0x58, 0x4C,
|
||||
0xAA, 0xED, 0x52, 0xF2, 0x4F, 0xF1, 0x66, 0xCF, 0xA5, 0x56, 0xEA, 0x7C, 0xE9, 0x63, 0xE7, 0x01,
|
||||
0xF9, 0xFE, 0x0C, 0x99, 0x2D, 0x0F, 0x3A, 0x41, 0x45, 0xA8, 0x30, 0x2B, 0x73, 0xBD, 0x86, 0x81,
|
||||
};
|
||||
|
||||
int swizzlecmp(const char *a, const char *b) {
|
||||
while (*a || *b) {
|
||||
int aa = swizzle[(unsigned char) *a];
|
||||
int bb = swizzle[(unsigned char) *b];
|
||||
ssize_t alen = strlen(a);
|
||||
ssize_t blen = strlen(b);
|
||||
|
||||
int cmp = aa - bb;
|
||||
if (cmp != 0) {
|
||||
return cmp;
|
||||
}
|
||||
|
||||
a++;
|
||||
b++;
|
||||
if (strcmp(a, b) == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
long long hash1 = 0, hash2 = 0;
|
||||
for (ssize_t i = alen - 1; i >= 0; i--) {
|
||||
hash1 = (hash1 * 37 + a[i]) & INT_MAX;
|
||||
}
|
||||
for (ssize_t i = blen - 1; i >= 0; i--) {
|
||||
hash2 = (hash2 * 37 + b[i]) & INT_MAX;
|
||||
}
|
||||
|
||||
int h1 = hash1, h2 = hash2;
|
||||
if (h1 == h2) {
|
||||
return strcmp(a, b);
|
||||
}
|
||||
|
||||
return h1 - h2;
|
||||
}
|
||||
|
||||
long long addpool(struct memfile *poolfile, struct memfile *treefile, const char *s, char type) {
|
||||
long long *sp = &treefile->tree;
|
||||
unsigned long *sp = &treefile->tree;
|
||||
size_t depth = 0;
|
||||
|
||||
// In typical data, traversal depth generally stays under 2.5x
|
||||
size_t max = 3 * log(treefile->off / sizeof(struct stringpool)) / log(2);
|
||||
if (max < 30) {
|
||||
max = 30;
|
||||
}
|
||||
|
||||
while (*sp != 0) {
|
||||
int cmp = swizzlecmp(s, poolfile->map + ((struct stringpool *) (treefile->map + *sp))->off + 1);
|
||||
@ -58,6 +54,23 @@ long long addpool(struct memfile *poolfile, struct memfile *treefile, const char
|
||||
} else {
|
||||
return ((struct stringpool *) (treefile->map + *sp))->off;
|
||||
}
|
||||
|
||||
depth++;
|
||||
if (depth > max) {
|
||||
// Search is very deep, so string is probably unique.
|
||||
// Add it to the pool without adding it to the search tree.
|
||||
|
||||
long long off = poolfile->off;
|
||||
if (memfile_write(poolfile, &type, 1) < 0) {
|
||||
perror("memfile write");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (memfile_write(poolfile, (void *) s, strlen(s) + 1) < 0) {
|
||||
perror("memfile write");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
return off;
|
||||
}
|
||||
}
|
||||
|
||||
// *sp is probably in the memory-mapped file, and will move if the file grows.
|
||||
@ -78,6 +91,16 @@ long long addpool(struct memfile *poolfile, struct memfile *treefile, const char
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (off >= LONG_MAX || treefile->off >= LONG_MAX) {
|
||||
// Tree or pool is bigger than 2GB
|
||||
static bool warned = false;
|
||||
if (!warned) {
|
||||
fprintf(stderr, "Warning: string pool is very large.\n");
|
||||
warned = true;
|
||||
}
|
||||
return off;
|
||||
}
|
||||
|
||||
struct stringpool tsp;
|
||||
tsp.left = 0;
|
||||
tsp.right = 0;
|
||||
|
11
pool.hpp
11
pool.hpp
@ -1,7 +1,12 @@
|
||||
#ifndef POOL_HPP
|
||||
#define POOL_HPP
|
||||
|
||||
struct stringpool {
|
||||
long long left;
|
||||
long long right;
|
||||
long long off;
|
||||
unsigned long left = 0;
|
||||
unsigned long right = 0;
|
||||
unsigned long off = 0;
|
||||
};
|
||||
|
||||
long long addpool(struct memfile *poolfile, struct memfile *treefile, const char *s, char type);
|
||||
|
||||
#endif
|
||||
|
123
projection.cpp
123
projection.cpp
@ -2,18 +2,39 @@
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
#include <atomic>
|
||||
#include "projection.hpp"
|
||||
|
||||
unsigned long long (*encode_index)(unsigned int wx, unsigned int wy) = NULL;
|
||||
void (*decode_index)(unsigned long long index, unsigned *wx, unsigned *wy) = NULL;
|
||||
|
||||
struct projection projections[] = {
|
||||
{"EPSG:4326", lonlat2tile, tile2lonlat, "urn:ogc:def:crs:OGC:1.3:CRS84"},
|
||||
{"EPSG:3857", epsg3857totile, tiletoepsg3857, "urn:ogc:def:crs:EPSG::3857"},
|
||||
{NULL, NULL},
|
||||
{NULL, NULL, NULL, NULL},
|
||||
};
|
||||
|
||||
struct projection *projection = &projections[0];
|
||||
|
||||
// http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
|
||||
void lonlat2tile(double lon, double lat, int zoom, long long *x, long long *y) {
|
||||
// Place infinite and NaN coordinates off the edge of the Mercator plane
|
||||
|
||||
int lat_class = fpclassify(lat);
|
||||
int lon_class = fpclassify(lon);
|
||||
bool bad_lon = false;
|
||||
|
||||
if (lat_class == FP_INFINITE || lat_class == FP_NAN) {
|
||||
lat = 89.9;
|
||||
}
|
||||
if (lon_class == FP_INFINITE || lon_class == FP_NAN) {
|
||||
// Keep these far enough from the plane that they don't get
|
||||
// moved back into it by 360-degree offsetting
|
||||
|
||||
lon = 720;
|
||||
bad_lon = true;
|
||||
}
|
||||
|
||||
// Must limit latitude somewhere to prevent overflow.
|
||||
// 89.9 degrees latitude is 0.621 worlds beyond the edge of the flat earth,
|
||||
// hopefully far enough out that there are few expectations about the shape.
|
||||
@ -24,10 +45,10 @@ void lonlat2tile(double lon, double lat, int zoom, long long *x, long long *y) {
|
||||
lat = 89.9;
|
||||
}
|
||||
|
||||
if (lon < -360) {
|
||||
if (lon < -360 && !bad_lon) {
|
||||
lon = -360;
|
||||
}
|
||||
if (lon > 360) {
|
||||
if (lon > 360 && !bad_lon) {
|
||||
lon = 360;
|
||||
}
|
||||
|
||||
@ -49,6 +70,18 @@ void tile2lonlat(long long x, long long y, int zoom, double *lon, double *lat) {
|
||||
}
|
||||
|
||||
void epsg3857totile(double ix, double iy, int zoom, long long *x, long long *y) {
|
||||
// Place infinite and NaN coordinates off the edge of the Mercator plane
|
||||
|
||||
int iy_class = fpclassify(iy);
|
||||
int ix_class = fpclassify(ix);
|
||||
|
||||
if (iy_class == FP_INFINITE || iy_class == FP_NAN) {
|
||||
iy = 40000000.0;
|
||||
}
|
||||
if (ix_class == FP_INFINITE || ix_class == FP_NAN) {
|
||||
ix = 40000000.0;
|
||||
}
|
||||
|
||||
*x = ix * (1LL << 31) / 6378137.0 / M_PI + (1LL << 31);
|
||||
*y = ((1LL << 32) - 1) - (iy * (1LL << 31) / 6378137.0 / M_PI + (1LL << 31));
|
||||
|
||||
@ -68,7 +101,60 @@ void tiletoepsg3857(long long ix, long long iy, int zoom, double *ox, double *oy
|
||||
*oy = ((1LL << 32) - 1 - iy - (1LL << 31)) * M_PI * 6378137.0 / (1LL << 31);
|
||||
}
|
||||
|
||||
unsigned long long encode(unsigned int wx, unsigned int wy) {
|
||||
// https://en.wikipedia.org/wiki/Hilbert_curve
|
||||
|
||||
void hilbert_rot(unsigned long long n, unsigned *x, unsigned *y, unsigned long long rx, unsigned long long ry) {
|
||||
if (ry == 0) {
|
||||
if (rx == 1) {
|
||||
*x = n - 1 - *x;
|
||||
*y = n - 1 - *y;
|
||||
}
|
||||
|
||||
unsigned t = *x;
|
||||
*x = *y;
|
||||
*y = t;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long long hilbert_xy2d(unsigned long long n, unsigned x, unsigned y) {
|
||||
unsigned long long d = 0;
|
||||
unsigned long long rx, ry;
|
||||
|
||||
for (unsigned long long s = n / 2; s > 0; s /= 2) {
|
||||
rx = (x & s) != 0;
|
||||
ry = (y & s) != 0;
|
||||
|
||||
d += s * s * ((3 * rx) ^ ry);
|
||||
hilbert_rot(s, &x, &y, rx, ry);
|
||||
}
|
||||
|
||||
return d;
|
||||
}
|
||||
|
||||
void hilbert_d2xy(unsigned long long n, unsigned long long d, unsigned *x, unsigned *y) {
|
||||
unsigned long long rx, ry;
|
||||
unsigned long long t = d;
|
||||
|
||||
*x = *y = 0;
|
||||
for (unsigned long long s = 1; s < n; s *= 2) {
|
||||
rx = 1 & (t / 2);
|
||||
ry = 1 & (t ^ rx);
|
||||
hilbert_rot(s, x, y, rx, ry);
|
||||
*x += s * rx;
|
||||
*y += s * ry;
|
||||
t /= 4;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long long encode_hilbert(unsigned int wx, unsigned int wy) {
|
||||
return hilbert_xy2d(1LL << 32, wx, wy);
|
||||
}
|
||||
|
||||
void decode_hilbert(unsigned long long index, unsigned *wx, unsigned *wy) {
|
||||
hilbert_d2xy(1LL << 32, index, wx, wy);
|
||||
}
|
||||
|
||||
unsigned long long encode_quadkey(unsigned int wx, unsigned int wy) {
|
||||
unsigned long long out = 0;
|
||||
|
||||
int i;
|
||||
@ -83,13 +169,32 @@ unsigned long long encode(unsigned int wx, unsigned int wy) {
|
||||
return out;
|
||||
}
|
||||
|
||||
void decode(unsigned long long index, unsigned *wx, unsigned *wy) {
|
||||
static std::atomic<unsigned char> decodex[256];
|
||||
static std::atomic<unsigned char> decodey[256];
|
||||
|
||||
void decode_quadkey(unsigned long long index, unsigned *wx, unsigned *wy) {
|
||||
static std::atomic<int> initialized(0);
|
||||
if (!initialized) {
|
||||
for (size_t ix = 0; ix < 256; ix++) {
|
||||
size_t xx = 0, yy = 0;
|
||||
|
||||
for (size_t i = 0; i < 32; i++) {
|
||||
xx |= ((ix >> (64 - 2 * (i + 1) + 1)) & 1) << (32 - (i + 1));
|
||||
yy |= ((ix >> (64 - 2 * (i + 1) + 0)) & 1) << (32 - (i + 1));
|
||||
}
|
||||
|
||||
decodex[ix] = xx;
|
||||
decodey[ix] = yy;
|
||||
}
|
||||
|
||||
initialized = 1;
|
||||
}
|
||||
|
||||
*wx = *wy = 0;
|
||||
|
||||
int i;
|
||||
for (i = 0; i < 32; i++) {
|
||||
*wx |= ((index >> (64 - 2 * (i + 1) + 1)) & 1) << (32 - (i + 1));
|
||||
*wy |= ((index >> (64 - 2 * (i + 1) + 0)) & 1) << (32 - (i + 1));
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
*wx |= ((unsigned) decodex[(index >> (8 * i)) & 0xFF]) << (4 * i);
|
||||
*wy |= ((unsigned) decodey[(index >> (8 * i)) & 0xFF]) << (4 * i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
#ifndef PROJECTION_HPP
|
||||
#define PROJECTION_HPP
|
||||
|
||||
void lonlat2tile(double lon, double lat, int zoom, long long *x, long long *y);
|
||||
void epsg3857totile(double ix, double iy, int zoom, long long *x, long long *y);
|
||||
void tile2lonlat(long long x, long long y, int zoom, double *lon, double *lat);
|
||||
void tiletoepsg3857(long long x, long long y, int zoom, double *ox, double *oy);
|
||||
unsigned long long encode(unsigned int wx, unsigned int wy);
|
||||
void decode(unsigned long long index, unsigned *wx, unsigned *wy);
|
||||
void set_projection_or_exit(const char *optarg);
|
||||
|
||||
struct projection {
|
||||
@ -15,3 +16,14 @@ struct projection {
|
||||
|
||||
extern struct projection *projection;
|
||||
extern struct projection projections[];
|
||||
|
||||
extern unsigned long long (*encode_index)(unsigned int wx, unsigned int wy);
|
||||
extern void (*decode_index)(unsigned long long index, unsigned *wx, unsigned *wy);
|
||||
|
||||
unsigned long long encode_quadkey(unsigned int wx, unsigned int wy);
|
||||
void decode_quadkey(unsigned long long index, unsigned *wx, unsigned *wy);
|
||||
|
||||
unsigned long long encode_hilbert(unsigned int wx, unsigned int wy);
|
||||
void decode_hilbert(unsigned long long index, unsigned *wx, unsigned *wy);
|
||||
|
||||
#endif
|
||||
|
@ -16,56 +16,69 @@ documentation.
|
||||
* @brief Contains functions to swap bytes in values (for different endianness).
|
||||
*/
|
||||
|
||||
#include <cstdint>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
|
||||
#include <protozero/config.hpp>
|
||||
|
||||
namespace protozero {
|
||||
namespace detail {
|
||||
|
||||
/**
|
||||
* Swap N byte value between endianness formats. This template function must
|
||||
* be specialized to actually work.
|
||||
*/
|
||||
template <int N>
|
||||
inline void byteswap(const char* /*data*/, char* /*result*/) {
|
||||
static_assert(N == 1, "Can only swap 4 or 8 byte values");
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap 4 byte value (int32_t, uint32_t, float) between endianness formats.
|
||||
*/
|
||||
template <>
|
||||
inline void byteswap<4>(const char* data, char* result) {
|
||||
inline uint32_t byteswap_impl(uint32_t value) noexcept {
|
||||
#ifdef PROTOZERO_USE_BUILTIN_BSWAP
|
||||
*reinterpret_cast<uint32_t*>(result) = __builtin_bswap32(*reinterpret_cast<const uint32_t*>(data));
|
||||
return __builtin_bswap32(value);
|
||||
#else
|
||||
result[3] = data[0];
|
||||
result[2] = data[1];
|
||||
result[1] = data[2];
|
||||
result[0] = data[3];
|
||||
return ((value & 0xff000000) >> 24) |
|
||||
((value & 0x00ff0000) >> 8) |
|
||||
((value & 0x0000ff00) << 8) |
|
||||
((value & 0x000000ff) << 24);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap 8 byte value (int64_t, uint64_t, double) between endianness formats.
|
||||
*/
|
||||
template <>
|
||||
inline void byteswap<8>(const char* data, char* result) {
|
||||
inline uint64_t byteswap_impl(uint64_t value) noexcept {
|
||||
#ifdef PROTOZERO_USE_BUILTIN_BSWAP
|
||||
*reinterpret_cast<uint64_t*>(result) = __builtin_bswap64(*reinterpret_cast<const uint64_t*>(data));
|
||||
return __builtin_bswap64(value);
|
||||
#else
|
||||
result[7] = data[0];
|
||||
result[6] = data[1];
|
||||
result[5] = data[2];
|
||||
result[4] = data[3];
|
||||
result[3] = data[4];
|
||||
result[2] = data[5];
|
||||
result[1] = data[6];
|
||||
result[0] = data[7];
|
||||
return ((value & 0xff00000000000000ULL) >> 56) |
|
||||
((value & 0x00ff000000000000ULL) >> 40) |
|
||||
((value & 0x0000ff0000000000ULL) >> 24) |
|
||||
((value & 0x000000ff00000000ULL) >> 8) |
|
||||
((value & 0x00000000ff000000ULL) << 8) |
|
||||
((value & 0x0000000000ff0000ULL) << 24) |
|
||||
((value & 0x000000000000ff00ULL) << 40) |
|
||||
((value & 0x00000000000000ffULL) << 56);
|
||||
#endif
|
||||
}
|
||||
|
||||
inline void byteswap_inplace(uint32_t* ptr) noexcept {
|
||||
*ptr = byteswap_impl(*ptr);
|
||||
}
|
||||
|
||||
inline void byteswap_inplace(uint64_t* ptr) noexcept {
|
||||
*ptr = byteswap_impl(*ptr);
|
||||
}
|
||||
|
||||
inline void byteswap_inplace(int32_t* ptr) noexcept {
|
||||
auto bptr = reinterpret_cast<uint32_t*>(ptr);
|
||||
*bptr = byteswap_impl(*bptr);
|
||||
}
|
||||
|
||||
inline void byteswap_inplace(int64_t* ptr) noexcept {
|
||||
auto bptr = reinterpret_cast<uint64_t*>(ptr);
|
||||
*bptr = byteswap_impl(*bptr);
|
||||
}
|
||||
|
||||
inline void byteswap_inplace(float* ptr) noexcept {
|
||||
auto bptr = reinterpret_cast<uint32_t*>(ptr);
|
||||
*bptr = byteswap_impl(*bptr);
|
||||
}
|
||||
|
||||
inline void byteswap_inplace(double* ptr) noexcept {
|
||||
auto bptr = reinterpret_cast<uint64_t*>(ptr);
|
||||
*bptr = byteswap_impl(*bptr);
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
} // end namespace protozero
|
||||
|
||||
#endif // PROTOZERO_BYTESWAP_HPP
|
||||
|
@ -35,17 +35,6 @@ documentation.
|
||||
# define PROTOZERO_BYTE_ORDER PROTOZERO_LITTLE_ENDIAN
|
||||
#endif
|
||||
|
||||
// On some ARM machines and depending on compiler settings access to unaligned
|
||||
// floating point values will result in a SIGBUS. Do not use the bare pointers
|
||||
// in this case.
|
||||
#if PROTOZERO_BYTE_ORDER == PROTOZERO_LITTLE_ENDIAN
|
||||
# if !defined(__arm__) && !defined(_M_ARM)
|
||||
# ifndef PROTOZERO_DO_NOT_USE_BARE_POINTER
|
||||
# define PROTOZERO_USE_BARE_POINTER_FOR_PACKED_FIXED
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Check whether __builtin_bswap is available
|
||||
#if defined(__GNUC__) || defined(__clang__)
|
||||
# define PROTOZERO_USE_BUILTIN_BSWAP
|
||||
|
@ -29,7 +29,7 @@ namespace protozero {
|
||||
*/
|
||||
struct exception : std::exception {
|
||||
/// Returns the explanatory string.
|
||||
const char *what() const noexcept override { return "pbf exception"; }
|
||||
const char* what() const noexcept override { return "pbf exception"; }
|
||||
};
|
||||
|
||||
/**
|
||||
@ -38,7 +38,7 @@ struct exception : std::exception {
|
||||
*/
|
||||
struct varint_too_long_exception : exception {
|
||||
/// Returns the explanatory string.
|
||||
const char *what() const noexcept override { return "varint too long exception"; }
|
||||
const char* what() const noexcept override { return "varint too long exception"; }
|
||||
};
|
||||
|
||||
/**
|
||||
@ -47,7 +47,7 @@ struct varint_too_long_exception : exception {
|
||||
*/
|
||||
struct unknown_pbf_wire_type_exception : exception {
|
||||
/// Returns the explanatory string.
|
||||
const char *what() const noexcept override { return "unknown pbf field type exception"; }
|
||||
const char* what() const noexcept override { return "unknown pbf field type exception"; }
|
||||
};
|
||||
|
||||
/**
|
||||
@ -60,7 +60,7 @@ struct unknown_pbf_wire_type_exception : exception {
|
||||
*/
|
||||
struct end_of_buffer_exception : exception {
|
||||
/// Returns the explanatory string.
|
||||
const char *what() const noexcept override { return "end of buffer exception"; }
|
||||
const char* what() const noexcept override { return "end of buffer exception"; }
|
||||
};
|
||||
|
||||
} // end namespace protozero
|
||||
|
328
protozero/iterators.hpp
Normal file
328
protozero/iterators.hpp
Normal file
@ -0,0 +1,328 @@
|
||||
#ifndef PROTOZERO_ITERATORS_HPP
|
||||
#define PROTOZERO_ITERATORS_HPP
|
||||
|
||||
/*****************************************************************************
|
||||
|
||||
protozero - Minimalistic protocol buffer decoder and encoder in C++.
|
||||
|
||||
This file is from https://github.com/mapbox/protozero where you can find more
|
||||
documentation.
|
||||
|
||||
*****************************************************************************/
|
||||
|
||||
/**
|
||||
* @file iterators.hpp
|
||||
*
|
||||
* @brief Contains the iterators for access to packed repeated fields.
|
||||
*/
|
||||
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <utility>
|
||||
|
||||
#include <protozero/config.hpp>
|
||||
#include <protozero/varint.hpp>
|
||||
|
||||
#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN
|
||||
# include <protozero/byteswap.hpp>
|
||||
#endif
|
||||
|
||||
namespace protozero {
|
||||
|
||||
/**
|
||||
* A range of iterators based on std::pair. Created from beginning and
|
||||
* end iterators. Used as a return type from some pbf_reader methods
|
||||
* that is easy to use with range-based for loops.
|
||||
*/
|
||||
template <typename T, typename P = std::pair<T, T>>
|
||||
class iterator_range :
|
||||
#ifdef PROTOZERO_STRICT_API
|
||||
protected
|
||||
#else
|
||||
public
|
||||
#endif
|
||||
P {
|
||||
|
||||
public:
|
||||
|
||||
/// The type of the iterators in this range.
|
||||
using iterator = T;
|
||||
|
||||
/// The value type of the underlying iterator.
|
||||
using value_type = typename std::iterator_traits<T>::value_type;
|
||||
|
||||
/**
|
||||
* Default constructor. Create empty iterator_range.
|
||||
*/
|
||||
constexpr iterator_range() :
|
||||
P(iterator{}, iterator{}) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create iterator range from two iterators.
|
||||
*
|
||||
* @param first_iterator Iterator to beginning or range.
|
||||
* @param last_iterator Iterator to end or range.
|
||||
*/
|
||||
constexpr iterator_range(iterator&& first_iterator, iterator&& last_iterator) :
|
||||
P(std::forward<iterator>(first_iterator),
|
||||
std::forward<iterator>(last_iterator)) {
|
||||
}
|
||||
|
||||
/// Return iterator to beginning of range.
|
||||
constexpr iterator begin() const noexcept {
|
||||
return this->first;
|
||||
}
|
||||
|
||||
/// Return iterator to end of range.
|
||||
constexpr iterator end() const noexcept {
|
||||
return this->second;
|
||||
}
|
||||
|
||||
/// Return iterator to beginning of range.
|
||||
constexpr iterator cbegin() const noexcept {
|
||||
return this->first;
|
||||
}
|
||||
|
||||
/// Return iterator to end of range.
|
||||
constexpr iterator cend() const noexcept {
|
||||
return this->second;
|
||||
}
|
||||
|
||||
/// Return true if this range is empty.
|
||||
constexpr std::size_t empty() const noexcept {
|
||||
return begin() == end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get element at the beginning of the range.
|
||||
*
|
||||
* @pre Range must not be empty.
|
||||
*/
|
||||
value_type front() const {
|
||||
protozero_assert(!empty());
|
||||
return *(this->first);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance beginning of range by one.
|
||||
*
|
||||
* @pre Range must not be empty.
|
||||
*/
|
||||
void drop_front() {
|
||||
protozero_assert(!empty());
|
||||
++this->first;
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the contents of this range with the other.
|
||||
*
|
||||
* @param other Other range to swap data with.
|
||||
*/
|
||||
void swap(iterator_range& other) noexcept {
|
||||
using std::swap;
|
||||
swap(this->first, other.first);
|
||||
swap(this->second, other.second);
|
||||
}
|
||||
|
||||
}; // struct iterator_range
|
||||
|
||||
/**
|
||||
* Swap two iterator_ranges.
|
||||
*
|
||||
* @param lhs First range.
|
||||
* @param rhs Second range.
|
||||
*/
|
||||
template <typename T>
|
||||
inline void swap(iterator_range<T>& lhs, iterator_range<T>& rhs) noexcept {
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* A forward iterator used for accessing packed repeated fields of fixed
|
||||
* length (fixed32, sfixed32, float, double).
|
||||
*/
|
||||
template <typename T>
|
||||
class const_fixed_iterator {
|
||||
|
||||
/// Pointer to current iterator position
|
||||
const char* m_data;
|
||||
|
||||
/// Pointer to end iterator position
|
||||
const char* m_end;
|
||||
|
||||
public:
|
||||
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = T;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
|
||||
const_fixed_iterator() noexcept :
|
||||
m_data(nullptr),
|
||||
m_end(nullptr) {
|
||||
}
|
||||
|
||||
const_fixed_iterator(const char* data, const char* end) noexcept :
|
||||
m_data(data),
|
||||
m_end(end) {
|
||||
}
|
||||
|
||||
const_fixed_iterator(const const_fixed_iterator&) noexcept = default;
|
||||
const_fixed_iterator(const_fixed_iterator&&) noexcept = default;
|
||||
|
||||
const_fixed_iterator& operator=(const const_fixed_iterator&) noexcept = default;
|
||||
const_fixed_iterator& operator=(const_fixed_iterator&&) noexcept = default;
|
||||
|
||||
~const_fixed_iterator() noexcept = default;
|
||||
|
||||
value_type operator*() const {
|
||||
value_type result;
|
||||
std::memcpy(&result, m_data, sizeof(value_type));
|
||||
#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN
|
||||
detail::byteswap_inplace(&result);
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
const_fixed_iterator& operator++() {
|
||||
m_data += sizeof(value_type);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_fixed_iterator operator++(int) {
|
||||
const const_fixed_iterator tmp(*this);
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator==(const const_fixed_iterator& rhs) const noexcept {
|
||||
return m_data == rhs.m_data && m_end == rhs.m_end;
|
||||
}
|
||||
|
||||
bool operator!=(const const_fixed_iterator& rhs) const noexcept {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
}; // class const_fixed_iterator
|
||||
|
||||
/**
|
||||
* A forward iterator used for accessing packed repeated varint fields
|
||||
* (int32, uint32, int64, uint64, bool, enum).
|
||||
*/
|
||||
template <typename T>
|
||||
class const_varint_iterator {
|
||||
|
||||
protected:
|
||||
|
||||
/// Pointer to current iterator position
|
||||
const char* m_data;
|
||||
|
||||
/// Pointer to end iterator position
|
||||
const char* m_end;
|
||||
|
||||
public:
|
||||
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = T;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
|
||||
const_varint_iterator() noexcept :
|
||||
m_data(nullptr),
|
||||
m_end(nullptr) {
|
||||
}
|
||||
|
||||
const_varint_iterator(const char* data, const char* end) noexcept :
|
||||
m_data(data),
|
||||
m_end(end) {
|
||||
}
|
||||
|
||||
const_varint_iterator(const const_varint_iterator&) noexcept = default;
|
||||
const_varint_iterator(const_varint_iterator&&) noexcept = default;
|
||||
|
||||
const_varint_iterator& operator=(const const_varint_iterator&) noexcept = default;
|
||||
const_varint_iterator& operator=(const_varint_iterator&&) noexcept = default;
|
||||
|
||||
~const_varint_iterator() noexcept = default;
|
||||
|
||||
value_type operator*() const {
|
||||
const char* d = m_data; // will be thrown away
|
||||
return static_cast<value_type>(decode_varint(&d, m_end));
|
||||
}
|
||||
|
||||
const_varint_iterator& operator++() {
|
||||
skip_varint(&m_data, m_end);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_varint_iterator operator++(int) {
|
||||
const const_varint_iterator tmp(*this);
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
bool operator==(const const_varint_iterator& rhs) const noexcept {
|
||||
return m_data == rhs.m_data && m_end == rhs.m_end;
|
||||
}
|
||||
|
||||
bool operator!=(const const_varint_iterator& rhs) const noexcept {
|
||||
return !(*this == rhs);
|
||||
}
|
||||
|
||||
}; // class const_varint_iterator
|
||||
|
||||
/**
|
||||
* A forward iterator used for accessing packed repeated svarint fields
|
||||
* (sint32, sint64).
|
||||
*/
|
||||
template <typename T>
|
||||
class const_svarint_iterator : public const_varint_iterator<T> {
|
||||
|
||||
public:
|
||||
|
||||
using iterator_category = std::forward_iterator_tag;
|
||||
using value_type = T;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = value_type*;
|
||||
using reference = value_type&;
|
||||
|
||||
const_svarint_iterator() noexcept :
|
||||
const_varint_iterator<T>() {
|
||||
}
|
||||
|
||||
const_svarint_iterator(const char* data, const char* end) noexcept :
|
||||
const_varint_iterator<T>(data, end) {
|
||||
}
|
||||
|
||||
const_svarint_iterator(const const_svarint_iterator&) = default;
|
||||
const_svarint_iterator(const_svarint_iterator&&) = default;
|
||||
|
||||
const_svarint_iterator& operator=(const const_svarint_iterator&) = default;
|
||||
const_svarint_iterator& operator=(const_svarint_iterator&&) = default;
|
||||
|
||||
~const_svarint_iterator() = default;
|
||||
|
||||
value_type operator*() const {
|
||||
const char* d = this->m_data; // will be thrown away
|
||||
return static_cast<value_type>(decode_zigzag64(decode_varint(&d, this->m_end)));
|
||||
}
|
||||
|
||||
const_svarint_iterator& operator++() {
|
||||
skip_varint(&this->m_data, this->m_end);
|
||||
return *this;
|
||||
}
|
||||
|
||||
const_svarint_iterator operator++(int) {
|
||||
const const_svarint_iterator tmp(*this);
|
||||
++(*this);
|
||||
return tmp;
|
||||
}
|
||||
|
||||
}; // class const_svarint_iterator
|
||||
|
||||
} // end namespace protozero
|
||||
|
||||
#endif // PROTOZERO_ITERATORS_HPP
|
@ -18,8 +18,8 @@ documentation.
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include <protozero/types.hpp>
|
||||
#include <protozero/pbf_writer.hpp>
|
||||
#include <protozero/types.hpp>
|
||||
|
||||
namespace protozero {
|
||||
|
||||
@ -46,7 +46,7 @@ public:
|
||||
|
||||
using enum_type = T;
|
||||
|
||||
pbf_builder(std::string& data) noexcept :
|
||||
explicit pbf_builder(std::string& data) noexcept :
|
||||
pbf_writer(data) {
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ public:
|
||||
|
||||
/// @cond INTERNAL
|
||||
#define PROTOZERO_WRITER_WRAP_ADD_SCALAR(name, type) \
|
||||
inline void add_##name(T tag, type value) { \
|
||||
void add_##name(T tag, type value) { \
|
||||
pbf_writer::add_##name(pbf_tag_type(tag), value); \
|
||||
}
|
||||
|
||||
@ -79,38 +79,59 @@ public:
|
||||
#undef PROTOZERO_WRITER_WRAP_ADD_SCALAR
|
||||
/// @endcond
|
||||
|
||||
inline void add_bytes(T tag, const char* value, std::size_t size) {
|
||||
void add_bytes(T tag, const char* value, std::size_t size) {
|
||||
pbf_writer::add_bytes(pbf_tag_type(tag), value, size);
|
||||
}
|
||||
|
||||
inline void add_bytes(T tag, const std::string& value) {
|
||||
void add_bytes(T tag, const data_view& value) {
|
||||
pbf_writer::add_bytes(pbf_tag_type(tag), value);
|
||||
}
|
||||
|
||||
inline void add_string(T tag, const char* value, std::size_t size) {
|
||||
void add_bytes(T tag, const std::string& value) {
|
||||
pbf_writer::add_bytes(pbf_tag_type(tag), value);
|
||||
}
|
||||
|
||||
void add_bytes(T tag, const char* value) {
|
||||
pbf_writer::add_bytes(pbf_tag_type(tag), value);
|
||||
}
|
||||
|
||||
template <typename... Ts>
|
||||
void add_bytes_vectored(T tag, Ts&&... values) {
|
||||
pbf_writer::add_bytes_vectored(pbf_tag_type(tag), std::forward<Ts>(values)...);
|
||||
}
|
||||
|
||||
void add_string(T tag, const char* value, std::size_t size) {
|
||||
pbf_writer::add_string(pbf_tag_type(tag), value, size);
|
||||
}
|
||||
|
||||
inline void add_string(T tag, const std::string& value) {
|
||||
void add_string(T tag, const data_view& value) {
|
||||
pbf_writer::add_string(pbf_tag_type(tag), value);
|
||||
}
|
||||
|
||||
inline void add_string(T tag, const char* value) {
|
||||
void add_string(T tag, const std::string& value) {
|
||||
pbf_writer::add_string(pbf_tag_type(tag), value);
|
||||
}
|
||||
|
||||
inline void add_message(T tag, const char* value, std::size_t size) {
|
||||
void add_string(T tag, const char* value) {
|
||||
pbf_writer::add_string(pbf_tag_type(tag), value);
|
||||
}
|
||||
|
||||
void add_message(T tag, const char* value, std::size_t size) {
|
||||
pbf_writer::add_message(pbf_tag_type(tag), value, size);
|
||||
}
|
||||
|
||||
inline void add_message(T tag, const std::string& value) {
|
||||
void add_message(T tag, const data_view& value) {
|
||||
pbf_writer::add_message(pbf_tag_type(tag), value);
|
||||
}
|
||||
|
||||
void add_message(T tag, const std::string& value) {
|
||||
pbf_writer::add_message(pbf_tag_type(tag), value);
|
||||
}
|
||||
|
||||
/// @cond INTERNAL
|
||||
#define PROTOZERO_WRITER_WRAP_ADD_PACKED(name) \
|
||||
template <typename InputIterator> \
|
||||
inline void add_packed_##name(T tag, InputIterator first, InputIterator last) { \
|
||||
void add_packed_##name(T tag, InputIterator first, InputIterator last) { \
|
||||
pbf_writer::add_packed_##name(pbf_tag_type(tag), first, last); \
|
||||
}
|
||||
|
||||
@ -132,7 +153,7 @@ public:
|
||||
#undef PROTOZERO_WRITER_WRAP_ADD_PACKED
|
||||
/// @endcond
|
||||
|
||||
};
|
||||
}; // class pbf_builder
|
||||
|
||||
} // end namespace protozero
|
||||
|
||||
|
@ -13,7 +13,7 @@ documentation.
|
||||
/**
|
||||
* @file pbf_message.hpp
|
||||
*
|
||||
* @brief Contains the pbf_message class.
|
||||
* @brief Contains the pbf_message template class.
|
||||
*/
|
||||
|
||||
#include <type_traits>
|
||||
@ -75,19 +75,23 @@ public:
|
||||
pbf_reader(std::forward<Args>(args)...) {
|
||||
}
|
||||
|
||||
inline bool next() {
|
||||
bool next() {
|
||||
return pbf_reader::next();
|
||||
}
|
||||
|
||||
inline bool next(T tag) {
|
||||
return pbf_reader::next(pbf_tag_type(tag));
|
||||
bool next(T next_tag) {
|
||||
return pbf_reader::next(pbf_tag_type(next_tag));
|
||||
}
|
||||
|
||||
inline T tag() const noexcept {
|
||||
bool next(T next_tag, pbf_wire_type type) {
|
||||
return pbf_reader::next(pbf_tag_type(next_tag), type);
|
||||
}
|
||||
|
||||
T tag() const noexcept {
|
||||
return T(pbf_reader::tag());
|
||||
}
|
||||
|
||||
};
|
||||
}; // class pbf_message
|
||||
|
||||
} // end namespace protozero
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -22,6 +22,7 @@ documentation.
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <protozero/config.hpp>
|
||||
#include <protozero/types.hpp>
|
||||
@ -68,38 +69,35 @@ class pbf_writer {
|
||||
// parent to the position where the data of the submessage is written to.
|
||||
std::size_t m_pos = 0;
|
||||
|
||||
inline void add_varint(uint64_t value) {
|
||||
void add_varint(uint64_t value) {
|
||||
protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
|
||||
protozero_assert(m_data);
|
||||
write_varint(std::back_inserter(*m_data), value);
|
||||
}
|
||||
|
||||
inline void add_field(pbf_tag_type tag, pbf_wire_type type) {
|
||||
void add_field(pbf_tag_type tag, pbf_wire_type type) {
|
||||
protozero_assert(((tag > 0 && tag < 19000) || (tag > 19999 && tag <= ((1 << 29) - 1))) && "tag out of range");
|
||||
uint32_t b = (tag << 3) | uint32_t(type);
|
||||
const uint32_t b = (tag << 3) | uint32_t(type);
|
||||
add_varint(b);
|
||||
}
|
||||
|
||||
inline void add_tagged_varint(pbf_tag_type tag, uint64_t value) {
|
||||
void add_tagged_varint(pbf_tag_type tag, uint64_t value) {
|
||||
add_field(tag, pbf_wire_type::varint);
|
||||
add_varint(value);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void add_fixed(T value) {
|
||||
void add_fixed(T value) {
|
||||
protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
|
||||
protozero_assert(m_data);
|
||||
#if PROTOZERO_BYTE_ORDER == PROTOZERO_LITTLE_ENDIAN
|
||||
m_data->append(reinterpret_cast<const char*>(&value), sizeof(T));
|
||||
#else
|
||||
auto size = m_data->size();
|
||||
m_data->resize(size + sizeof(T));
|
||||
byteswap<sizeof(T)>(reinterpret_cast<const char*>(&value), const_cast<char*>(m_data->data() + size));
|
||||
#if PROTOZERO_BYTE_ORDER != PROTOZERO_LITTLE_ENDIAN
|
||||
detail::byteswap_inplace(&value);
|
||||
#endif
|
||||
m_data->append(reinterpret_cast<const char*>(&value), sizeof(T));
|
||||
}
|
||||
|
||||
template <typename T, typename It>
|
||||
inline void add_packed_fixed(pbf_tag_type tag, It first, It last, std::input_iterator_tag) {
|
||||
void add_packed_fixed(pbf_tag_type tag, It first, It last, std::input_iterator_tag) {
|
||||
if (first == last) {
|
||||
return;
|
||||
}
|
||||
@ -112,12 +110,12 @@ class pbf_writer {
|
||||
}
|
||||
|
||||
template <typename T, typename It>
|
||||
inline void add_packed_fixed(pbf_tag_type tag, It first, It last, std::forward_iterator_tag) {
|
||||
void add_packed_fixed(pbf_tag_type tag, It first, It last, std::forward_iterator_tag) {
|
||||
if (first == last) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto length = std::distance(first, last);
|
||||
const auto length = std::distance(first, last);
|
||||
add_length_varint(tag, sizeof(T) * pbf_length_type(length));
|
||||
reserve(sizeof(T) * std::size_t(length));
|
||||
|
||||
@ -127,7 +125,7 @@ class pbf_writer {
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
inline void add_packed_varint(pbf_tag_type tag, It first, It last) {
|
||||
void add_packed_varint(pbf_tag_type tag, It first, It last) {
|
||||
if (first == last) {
|
||||
return;
|
||||
}
|
||||
@ -140,7 +138,7 @@ class pbf_writer {
|
||||
}
|
||||
|
||||
template <typename It>
|
||||
inline void add_packed_svarint(pbf_tag_type tag, It first, It last) {
|
||||
void add_packed_svarint(pbf_tag_type tag, It first, It last) {
|
||||
if (first == last) {
|
||||
return;
|
||||
}
|
||||
@ -155,14 +153,18 @@ class pbf_writer {
|
||||
// The number of bytes to reserve for the varint holding the length of
|
||||
// a length-delimited field. The length has to fit into pbf_length_type,
|
||||
// and a varint needs 8 bit for every 7 bit.
|
||||
static const int reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1;
|
||||
enum constant_reserve_bytes : int {
|
||||
reserve_bytes = sizeof(pbf_length_type) * 8 / 7 + 1
|
||||
};
|
||||
|
||||
// If m_rollpack_pos is set to this special value, it means that when
|
||||
// the submessage is closed, nothing needs to be done, because the length
|
||||
// of the submessage has already been written correctly.
|
||||
static const std::size_t size_is_known = std::numeric_limits<std::size_t>::max();
|
||||
enum constant_size_is_known : std::size_t {
|
||||
size_is_known = std::numeric_limits<std::size_t>::max()
|
||||
};
|
||||
|
||||
inline void open_submessage(pbf_tag_type tag, std::size_t size) {
|
||||
void open_submessage(pbf_tag_type tag, std::size_t size) {
|
||||
protozero_assert(m_pos == 0);
|
||||
protozero_assert(m_data);
|
||||
if (size == 0) {
|
||||
@ -177,7 +179,7 @@ class pbf_writer {
|
||||
m_pos = m_data->size();
|
||||
}
|
||||
|
||||
inline void rollback_submessage() {
|
||||
void rollback_submessage() {
|
||||
protozero_assert(m_pos != 0);
|
||||
protozero_assert(m_rollback_pos != size_is_known);
|
||||
protozero_assert(m_data);
|
||||
@ -185,20 +187,20 @@ class pbf_writer {
|
||||
m_pos = 0;
|
||||
}
|
||||
|
||||
inline void commit_submessage() {
|
||||
void commit_submessage() {
|
||||
protozero_assert(m_pos != 0);
|
||||
protozero_assert(m_rollback_pos != size_is_known);
|
||||
protozero_assert(m_data);
|
||||
auto length = pbf_length_type(m_data->size() - m_pos);
|
||||
const auto length = pbf_length_type(m_data->size() - m_pos);
|
||||
|
||||
protozero_assert(m_data->size() >= m_pos - reserve_bytes);
|
||||
auto n = write_varint(m_data->begin() + long(m_pos) - reserve_bytes, length);
|
||||
const auto n = write_varint(m_data->begin() + long(m_pos) - reserve_bytes, length);
|
||||
|
||||
m_data->erase(m_data->begin() + long(m_pos) - reserve_bytes + n, m_data->begin() + long(m_pos));
|
||||
m_pos = 0;
|
||||
}
|
||||
|
||||
inline void close_submessage() {
|
||||
void close_submessage() {
|
||||
protozero_assert(m_data);
|
||||
if (m_pos == 0 || m_rollback_pos == size_is_known) {
|
||||
return;
|
||||
@ -210,7 +212,7 @@ class pbf_writer {
|
||||
}
|
||||
}
|
||||
|
||||
inline void add_length_varint(pbf_tag_type tag, pbf_length_type length) {
|
||||
void add_length_varint(pbf_tag_type tag, pbf_length_type length) {
|
||||
add_field(tag, pbf_wire_type::length_delimited);
|
||||
add_varint(length);
|
||||
}
|
||||
@ -222,20 +224,18 @@ public:
|
||||
* stores a reference to that string and adds all data to it. The string
|
||||
* doesn't have to be empty. The pbf_writer will just append data.
|
||||
*/
|
||||
inline explicit pbf_writer(std::string& data) noexcept :
|
||||
explicit pbf_writer(std::string& data) noexcept :
|
||||
m_data(&data),
|
||||
m_parent_writer(nullptr),
|
||||
m_pos(0) {
|
||||
m_parent_writer(nullptr) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a writer without a data store. In this form the writer can not
|
||||
* be used!
|
||||
*/
|
||||
inline pbf_writer() noexcept :
|
||||
pbf_writer() noexcept :
|
||||
m_data(nullptr),
|
||||
m_parent_writer(nullptr),
|
||||
m_pos(0) {
|
||||
m_parent_writer(nullptr) {
|
||||
}
|
||||
|
||||
/**
|
||||
@ -248,10 +248,9 @@ public:
|
||||
* Setting this allows some optimizations but is only possible in
|
||||
* a few very specific cases.
|
||||
*/
|
||||
inline pbf_writer(pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size=0) :
|
||||
pbf_writer(pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size=0) :
|
||||
m_data(parent_writer.m_data),
|
||||
m_parent_writer(&parent_writer),
|
||||
m_pos(0) {
|
||||
m_parent_writer(&parent_writer) {
|
||||
m_parent_writer->open_submessage(tag, size);
|
||||
}
|
||||
|
||||
@ -262,17 +261,30 @@ public:
|
||||
pbf_writer& operator=(const pbf_writer&) noexcept = default;
|
||||
|
||||
/// A pbf_writer object can be moved
|
||||
inline pbf_writer(pbf_writer&&) noexcept = default;
|
||||
pbf_writer(pbf_writer&&) noexcept = default;
|
||||
|
||||
/// A pbf_writer object can be moved
|
||||
inline pbf_writer& operator=(pbf_writer&&) noexcept = default;
|
||||
pbf_writer& operator=(pbf_writer&&) noexcept = default;
|
||||
|
||||
inline ~pbf_writer() {
|
||||
~pbf_writer() {
|
||||
if (m_parent_writer) {
|
||||
m_parent_writer->close_submessage();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the contents of this object with the other.
|
||||
*
|
||||
* @param other Other object to swap data with.
|
||||
*/
|
||||
void swap(pbf_writer& other) noexcept {
|
||||
using std::swap;
|
||||
swap(m_data, other.m_data);
|
||||
swap(m_parent_writer, other.m_parent_writer);
|
||||
swap(m_rollback_pos, other.m_rollback_pos);
|
||||
swap(m_pos, other.m_pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reserve size bytes in the underlying message store in addition to
|
||||
* whatever the message store already holds. So unlike
|
||||
@ -286,7 +298,14 @@ public:
|
||||
m_data->reserve(m_data->size() + size);
|
||||
}
|
||||
|
||||
inline void rollback() {
|
||||
/**
|
||||
* Cancel writing of this submessage. The complete submessage will be
|
||||
* removed as if it was never created and no fields were added.
|
||||
*
|
||||
* @pre Must be a pbf_writer of a submessage, ie one opened with the
|
||||
* pbf_writer constructor taking a parent message.
|
||||
*/
|
||||
void rollback() {
|
||||
protozero_assert(m_parent_writer && "you can't call rollback() on a pbf_writer without a parent");
|
||||
protozero_assert(m_pos == 0 && "you can't call rollback() on a pbf_writer that has an open nested submessage");
|
||||
m_parent_writer->rollback_submessage();
|
||||
@ -304,7 +323,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_bool(pbf_tag_type tag, bool value) {
|
||||
void add_bool(pbf_tag_type tag, bool value) {
|
||||
add_field(tag, pbf_wire_type::varint);
|
||||
protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
|
||||
protozero_assert(m_data);
|
||||
@ -317,7 +336,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_enum(pbf_tag_type tag, int32_t value) {
|
||||
void add_enum(pbf_tag_type tag, int32_t value) {
|
||||
add_tagged_varint(tag, uint64_t(value));
|
||||
}
|
||||
|
||||
@ -327,7 +346,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_int32(pbf_tag_type tag, int32_t value) {
|
||||
void add_int32(pbf_tag_type tag, int32_t value) {
|
||||
add_tagged_varint(tag, uint64_t(value));
|
||||
}
|
||||
|
||||
@ -337,7 +356,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_sint32(pbf_tag_type tag, int32_t value) {
|
||||
void add_sint32(pbf_tag_type tag, int32_t value) {
|
||||
add_tagged_varint(tag, encode_zigzag32(value));
|
||||
}
|
||||
|
||||
@ -347,7 +366,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_uint32(pbf_tag_type tag, uint32_t value) {
|
||||
void add_uint32(pbf_tag_type tag, uint32_t value) {
|
||||
add_tagged_varint(tag, value);
|
||||
}
|
||||
|
||||
@ -357,7 +376,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_int64(pbf_tag_type tag, int64_t value) {
|
||||
void add_int64(pbf_tag_type tag, int64_t value) {
|
||||
add_tagged_varint(tag, uint64_t(value));
|
||||
}
|
||||
|
||||
@ -367,7 +386,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_sint64(pbf_tag_type tag, int64_t value) {
|
||||
void add_sint64(pbf_tag_type tag, int64_t value) {
|
||||
add_tagged_varint(tag, encode_zigzag64(value));
|
||||
}
|
||||
|
||||
@ -377,7 +396,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_uint64(pbf_tag_type tag, uint64_t value) {
|
||||
void add_uint64(pbf_tag_type tag, uint64_t value) {
|
||||
add_tagged_varint(tag, value);
|
||||
}
|
||||
|
||||
@ -387,7 +406,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_fixed32(pbf_tag_type tag, uint32_t value) {
|
||||
void add_fixed32(pbf_tag_type tag, uint32_t value) {
|
||||
add_field(tag, pbf_wire_type::fixed32);
|
||||
add_fixed<uint32_t>(value);
|
||||
}
|
||||
@ -398,7 +417,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_sfixed32(pbf_tag_type tag, int32_t value) {
|
||||
void add_sfixed32(pbf_tag_type tag, int32_t value) {
|
||||
add_field(tag, pbf_wire_type::fixed32);
|
||||
add_fixed<int32_t>(value);
|
||||
}
|
||||
@ -409,7 +428,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_fixed64(pbf_tag_type tag, uint64_t value) {
|
||||
void add_fixed64(pbf_tag_type tag, uint64_t value) {
|
||||
add_field(tag, pbf_wire_type::fixed64);
|
||||
add_fixed<uint64_t>(value);
|
||||
}
|
||||
@ -420,7 +439,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_sfixed64(pbf_tag_type tag, int64_t value) {
|
||||
void add_sfixed64(pbf_tag_type tag, int64_t value) {
|
||||
add_field(tag, pbf_wire_type::fixed64);
|
||||
add_fixed<int64_t>(value);
|
||||
}
|
||||
@ -431,7 +450,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_float(pbf_tag_type tag, float value) {
|
||||
void add_float(pbf_tag_type tag, float value) {
|
||||
add_field(tag, pbf_wire_type::fixed32);
|
||||
add_fixed<float>(value);
|
||||
}
|
||||
@ -442,7 +461,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_double(pbf_tag_type tag, double value) {
|
||||
void add_double(pbf_tag_type tag, double value) {
|
||||
add_field(tag, pbf_wire_type::fixed64);
|
||||
add_fixed<double>(value);
|
||||
}
|
||||
@ -454,7 +473,7 @@ public:
|
||||
* @param value Pointer to value to be written
|
||||
* @param size Number of bytes to be written
|
||||
*/
|
||||
inline void add_bytes(pbf_tag_type tag, const char* value, std::size_t size) {
|
||||
void add_bytes(pbf_tag_type tag, const char* value, std::size_t size) {
|
||||
protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
|
||||
protozero_assert(m_data);
|
||||
protozero_assert(size <= std::numeric_limits<pbf_length_type>::max());
|
||||
@ -468,10 +487,62 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_bytes(pbf_tag_type tag, const std::string& value) {
|
||||
void add_bytes(pbf_tag_type tag, const data_view& value) {
|
||||
add_bytes(tag, value.data(), value.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "bytes" field to data.
|
||||
*
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
void add_bytes(pbf_tag_type tag, const std::string& value) {
|
||||
add_bytes(tag, value.data(), value.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "bytes" field to data. Bytes from the value are written until
|
||||
* a null byte is encountered. The null byte is not added.
|
||||
*
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Pointer to zero-delimited value to be written
|
||||
*/
|
||||
void add_bytes(pbf_tag_type tag, const char* value) {
|
||||
add_bytes(tag, value, std::strlen(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "bytes" field to data using vectored input. All the data in the
|
||||
* 2nd and further arguments is "concatenated" with only a single copy
|
||||
* into the final buffer.
|
||||
*
|
||||
* This will work with objects of any type supporting the data() and
|
||||
* size() methods like std::string or protozero::data_view.
|
||||
*
|
||||
* Example:
|
||||
* @code
|
||||
* std::string data1 = "abc";
|
||||
* std::string data2 = "xyz";
|
||||
* writer.add_bytes_vectored(1, data1, data2);
|
||||
* @endcode
|
||||
*
|
||||
* @tparam Ts List of types supporting data() and size() methods.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param values List of objects of types Ts with data to be appended.
|
||||
*/
|
||||
template <typename... Ts>
|
||||
void add_bytes_vectored(pbf_tag_type tag, Ts&&... values) {
|
||||
protozero_assert(m_pos == 0 && "you can't add fields to a parent pbf_writer if there is an existing pbf_writer for a submessage");
|
||||
protozero_assert(m_data);
|
||||
size_t sum_size = 0;
|
||||
(void)std::initializer_list<size_t>{sum_size += values.size()...};
|
||||
protozero_assert(sum_size <= std::numeric_limits<pbf_length_type>::max());
|
||||
add_length_varint(tag, pbf_length_type(sum_size));
|
||||
m_data->reserve(m_data->size() + sum_size);
|
||||
(void)std::initializer_list<int>{(m_data->append(values.data(), values.size()), 0)...};
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "string" field to data.
|
||||
*
|
||||
@ -479,7 +550,7 @@ public:
|
||||
* @param value Pointer to value to be written
|
||||
* @param size Number of bytes to be written
|
||||
*/
|
||||
inline void add_string(pbf_tag_type tag, const char* value, std::size_t size) {
|
||||
void add_string(pbf_tag_type tag, const char* value, std::size_t size) {
|
||||
add_bytes(tag, value, size);
|
||||
}
|
||||
|
||||
@ -489,7 +560,17 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
inline void add_string(pbf_tag_type tag, const std::string& value) {
|
||||
void add_string(pbf_tag_type tag, const data_view& value) {
|
||||
add_bytes(tag, value.data(), value.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "string" field to data.
|
||||
*
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written
|
||||
*/
|
||||
void add_string(pbf_tag_type tag, const std::string& value) {
|
||||
add_bytes(tag, value.data(), value.size());
|
||||
}
|
||||
|
||||
@ -500,7 +581,7 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Pointer to value to be written
|
||||
*/
|
||||
inline void add_string(pbf_tag_type tag, const char* value) {
|
||||
void add_string(pbf_tag_type tag, const char* value) {
|
||||
add_bytes(tag, value, std::strlen(value));
|
||||
}
|
||||
|
||||
@ -511,7 +592,7 @@ public:
|
||||
* @param value Pointer to message to be written
|
||||
* @param size Length of the message
|
||||
*/
|
||||
inline void add_message(pbf_tag_type tag, const char* value, std::size_t size) {
|
||||
void add_message(pbf_tag_type tag, const char* value, std::size_t size) {
|
||||
add_bytes(tag, value, size);
|
||||
}
|
||||
|
||||
@ -521,7 +602,17 @@ public:
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written. The value must be a complete message.
|
||||
*/
|
||||
inline void add_message(pbf_tag_type tag, const std::string& value) {
|
||||
void add_message(pbf_tag_type tag, const data_view& value) {
|
||||
add_bytes(tag, value.data(), value.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "message" field to data.
|
||||
*
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param value Value to be written. The value must be a complete message.
|
||||
*/
|
||||
void add_message(pbf_tag_type tag, const std::string& value) {
|
||||
add_bytes(tag, value.data(), value.size());
|
||||
}
|
||||
|
||||
@ -535,126 +626,126 @@ public:
|
||||
/**
|
||||
* Add "repeated packed bool" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to bool.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_bool(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_bool(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_varint(tag, first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "repeated packed enum" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to int32_t.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_enum(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_enum(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_varint(tag, first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "repeated packed int32" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to int32_t.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_int32(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_int32(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_varint(tag, first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "repeated packed sint32" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to int32_t.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_sint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_sint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_svarint(tag, first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "repeated packed uint32" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to uint32_t.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_uint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_uint32(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_varint(tag, first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "repeated packed int64" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to int64_t.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_int64(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_int64(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_varint(tag, first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "repeated packed sint64" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to int64_t.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_sint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_sint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_svarint(tag, first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "repeated packed uint64" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to uint64_t.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_uint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_uint64(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_varint(tag, first, last);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add "repeated packed fixed32" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to uint32_t.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_fixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_fixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_fixed<uint32_t, InputIterator>(tag, first, last,
|
||||
typename std::iterator_traits<InputIterator>::iterator_category());
|
||||
}
|
||||
@ -662,14 +753,14 @@ public:
|
||||
/**
|
||||
* Add "repeated packed sfixed32" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to int32_t.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_sfixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_sfixed32(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_fixed<int32_t, InputIterator>(tag, first, last,
|
||||
typename std::iterator_traits<InputIterator>::iterator_category());
|
||||
}
|
||||
@ -677,14 +768,14 @@ public:
|
||||
/**
|
||||
* Add "repeated packed fixed64" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to uint64_t.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_fixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_fixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_fixed<uint64_t, InputIterator>(tag, first, last,
|
||||
typename std::iterator_traits<InputIterator>::iterator_category());
|
||||
}
|
||||
@ -692,14 +783,14 @@ public:
|
||||
/**
|
||||
* Add "repeated packed sfixed64" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to int64_t.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_sfixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_sfixed64(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_fixed<int64_t, InputIterator>(tag, first, last,
|
||||
typename std::iterator_traits<InputIterator>::iterator_category());
|
||||
}
|
||||
@ -707,14 +798,14 @@ public:
|
||||
/**
|
||||
* Add "repeated packed float" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to float.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_float(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_float(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_fixed<float, InputIterator>(tag, first, last,
|
||||
typename std::iterator_traits<InputIterator>::iterator_category());
|
||||
}
|
||||
@ -722,14 +813,14 @@ public:
|
||||
/**
|
||||
* Add "repeated packed double" field to data.
|
||||
*
|
||||
* @tparam InputIterator An type satisfying the InputIterator concept.
|
||||
* @tparam InputIterator A type satisfying the InputIterator concept.
|
||||
* Dereferencing the iterator must yield a type assignable to double.
|
||||
* @param tag Tag (field number) of the field
|
||||
* @param first Iterator pointing to the beginning of the data
|
||||
* @param last Iterator pointing one past the end of data
|
||||
*/
|
||||
template <typename InputIterator>
|
||||
inline void add_packed_double(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
void add_packed_double(pbf_tag_type tag, InputIterator first, InputIterator last) {
|
||||
add_packed_fixed<double, InputIterator>(tag, first, last,
|
||||
typename std::iterator_traits<InputIterator>::iterator_category());
|
||||
}
|
||||
@ -742,6 +833,16 @@ public:
|
||||
|
||||
}; // class pbf_writer
|
||||
|
||||
/**
|
||||
* Swap two pbf_writer objects.
|
||||
*
|
||||
* @param lhs First object.
|
||||
* @param rhs Second object.
|
||||
*/
|
||||
inline void swap(pbf_writer& lhs, pbf_writer& rhs) noexcept {
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
class packed_field {
|
||||
@ -752,6 +853,12 @@ namespace detail {
|
||||
|
||||
public:
|
||||
|
||||
packed_field(const packed_field&) = delete;
|
||||
packed_field& operator=(const packed_field&) = delete;
|
||||
|
||||
packed_field(packed_field&&) = default;
|
||||
packed_field& operator=(packed_field&&) = default;
|
||||
|
||||
packed_field(pbf_writer& parent_writer, pbf_tag_type tag) :
|
||||
m_writer(parent_writer, tag) {
|
||||
}
|
||||
@ -771,12 +878,14 @@ namespace detail {
|
||||
|
||||
public:
|
||||
|
||||
packed_field_fixed(pbf_writer& parent_writer, pbf_tag_type tag) :
|
||||
packed_field(parent_writer, tag) {
|
||||
template <typename P>
|
||||
packed_field_fixed(pbf_writer& parent_writer, P tag) :
|
||||
packed_field(parent_writer, static_cast<pbf_tag_type>(tag)) {
|
||||
}
|
||||
|
||||
packed_field_fixed(pbf_writer& parent_writer, pbf_tag_type tag, std::size_t size) :
|
||||
packed_field(parent_writer, tag, size * sizeof(T)) {
|
||||
template <typename P>
|
||||
packed_field_fixed(pbf_writer& parent_writer, P tag, std::size_t size) :
|
||||
packed_field(parent_writer, static_cast<pbf_tag_type>(tag), size * sizeof(T)) {
|
||||
}
|
||||
|
||||
void add_element(T value) {
|
||||
@ -790,8 +899,9 @@ namespace detail {
|
||||
|
||||
public:
|
||||
|
||||
packed_field_varint(pbf_writer& parent_writer, pbf_tag_type tag) :
|
||||
packed_field(parent_writer, tag) {
|
||||
template <typename P>
|
||||
packed_field_varint(pbf_writer& parent_writer, P tag) :
|
||||
packed_field(parent_writer, static_cast<pbf_tag_type>(tag)) {
|
||||
}
|
||||
|
||||
void add_element(T value) {
|
||||
@ -805,8 +915,9 @@ namespace detail {
|
||||
|
||||
public:
|
||||
|
||||
packed_field_svarint(pbf_writer& parent_writer, pbf_tag_type tag) :
|
||||
packed_field(parent_writer, tag) {
|
||||
template <typename P>
|
||||
packed_field_svarint(pbf_writer& parent_writer, P tag) :
|
||||
packed_field(parent_writer, static_cast<pbf_tag_type>(tag)) {
|
||||
}
|
||||
|
||||
void add_element(T value) {
|
||||
@ -817,19 +928,46 @@ namespace detail {
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
/// Class for generating packed repeated bool fields.
|
||||
using packed_field_bool = detail::packed_field_varint<bool>;
|
||||
|
||||
/// Class for generating packed repeated enum fields.
|
||||
using packed_field_enum = detail::packed_field_varint<int32_t>;
|
||||
|
||||
/// Class for generating packed repeated int32 fields.
|
||||
using packed_field_int32 = detail::packed_field_varint<int32_t>;
|
||||
|
||||
/// Class for generating packed repeated sint32 fields.
|
||||
using packed_field_sint32 = detail::packed_field_svarint<int32_t>;
|
||||
|
||||
/// Class for generating packed repeated uint32 fields.
|
||||
using packed_field_uint32 = detail::packed_field_varint<uint32_t>;
|
||||
|
||||
/// Class for generating packed repeated int64 fields.
|
||||
using packed_field_int64 = detail::packed_field_varint<int64_t>;
|
||||
|
||||
/// Class for generating packed repeated sint64 fields.
|
||||
using packed_field_sint64 = detail::packed_field_svarint<int64_t>;
|
||||
|
||||
/// Class for generating packed repeated uint64 fields.
|
||||
using packed_field_uint64 = detail::packed_field_varint<uint64_t>;
|
||||
|
||||
/// Class for generating packed repeated fixed32 fields.
|
||||
using packed_field_fixed32 = detail::packed_field_fixed<uint32_t>;
|
||||
|
||||
/// Class for generating packed repeated sfixed32 fields.
|
||||
using packed_field_sfixed32 = detail::packed_field_fixed<int32_t>;
|
||||
|
||||
/// Class for generating packed repeated fixed64 fields.
|
||||
using packed_field_fixed64 = detail::packed_field_fixed<uint64_t>;
|
||||
|
||||
/// Class for generating packed repeated sfixed64 fields.
|
||||
using packed_field_sfixed64 = detail::packed_field_fixed<int64_t>;
|
||||
|
||||
/// Class for generating packed repeated float fields.
|
||||
using packed_field_float = detail::packed_field_fixed<float>;
|
||||
|
||||
/// Class for generating packed repeated double fields.
|
||||
using packed_field_double = detail::packed_field_fixed<double>;
|
||||
|
||||
} // end namespace protozero
|
||||
|
@ -16,33 +16,190 @@ documentation.
|
||||
* @brief Contains the declaration of low-level types used in the pbf format.
|
||||
*/
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include <protozero/config.hpp>
|
||||
|
||||
namespace protozero {
|
||||
|
||||
/**
|
||||
* The type used for field tags (field numbers).
|
||||
*/
|
||||
typedef uint32_t pbf_tag_type;
|
||||
/**
|
||||
* The type used for field tags (field numbers).
|
||||
*/
|
||||
using pbf_tag_type = uint32_t;
|
||||
|
||||
/**
|
||||
* The type used to encode type information.
|
||||
* See the table on
|
||||
* https://developers.google.com/protocol-buffers/docs/encoding
|
||||
*/
|
||||
enum class pbf_wire_type : uint32_t {
|
||||
varint = 0, // int32/64, uint32/64, sint32/64, bool, enum
|
||||
fixed64 = 1, // fixed64, sfixed64, double
|
||||
length_delimited = 2, // string, bytes, embedded messages,
|
||||
// packed repeated fields
|
||||
fixed32 = 5, // fixed32, sfixed32, float
|
||||
unknown = 99 // used for default setting in this library
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the tag and wire type of the current field in one integer suitable
|
||||
* for comparison with a switch statement.
|
||||
*
|
||||
* See pbf_reader.tag_and_type() for an example how to use this.
|
||||
*/
|
||||
template <typename T>
|
||||
constexpr inline uint32_t tag_and_type(T tag, pbf_wire_type wire_type) noexcept {
|
||||
return (static_cast<uint32_t>(static_cast<pbf_tag_type>(tag)) << 3) | static_cast<uint32_t>(wire_type);
|
||||
}
|
||||
|
||||
/**
|
||||
* The type used for length values, such as the length of a field.
|
||||
*/
|
||||
using pbf_length_type = uint32_t;
|
||||
|
||||
#ifdef PROTOZERO_USE_VIEW
|
||||
using data_view = PROTOZERO_USE_VIEW;
|
||||
#else
|
||||
|
||||
/**
|
||||
* Holds a pointer to some data and a length.
|
||||
*
|
||||
* This class is supposed to be compatible with the std::string_view
|
||||
* that will be available in C++17.
|
||||
*/
|
||||
class data_view {
|
||||
|
||||
const char* m_data;
|
||||
std::size_t m_size;
|
||||
|
||||
public:
|
||||
|
||||
/**
|
||||
* The type used to encode type information.
|
||||
* See the table on
|
||||
* https://developers.google.com/protocol-buffers/docs/encoding
|
||||
* Default constructor. Construct an empty data_view.
|
||||
*/
|
||||
enum class pbf_wire_type : uint32_t {
|
||||
varint = 0, // int32/64, uint32/64, sint32/64, bool, enum
|
||||
fixed64 = 1, // fixed64, sfixed64, double
|
||||
length_delimited = 2, // string, bytes, embedded messages,
|
||||
// packed repeated fields
|
||||
fixed32 = 5, // fixed32, sfixed32, float
|
||||
unknown = 99 // used for default setting in this library
|
||||
};
|
||||
constexpr data_view() noexcept
|
||||
: m_data(nullptr),
|
||||
m_size(0) {
|
||||
}
|
||||
|
||||
/**
|
||||
* The type used for length values, such as the length of a field.
|
||||
* Create data_view from pointer and size.
|
||||
*
|
||||
* @param ptr Pointer to the data.
|
||||
* @param length Length of the data.
|
||||
*/
|
||||
typedef uint32_t pbf_length_type;
|
||||
constexpr data_view(const char* ptr, std::size_t length) noexcept
|
||||
: m_data(ptr),
|
||||
m_size(length) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create data_view from string.
|
||||
*
|
||||
* @param str String with the data.
|
||||
*/
|
||||
data_view(const std::string& str) noexcept
|
||||
: m_data(str.data()),
|
||||
m_size(str.size()) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create data_view from zero-terminated string.
|
||||
*
|
||||
* @param ptr Pointer to the data.
|
||||
*/
|
||||
data_view(const char* ptr) noexcept
|
||||
: m_data(ptr),
|
||||
m_size(std::strlen(ptr)) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Swap the contents of this object with the other.
|
||||
*
|
||||
* @param other Other object to swap data with.
|
||||
*/
|
||||
void swap(data_view& other) noexcept {
|
||||
using std::swap;
|
||||
swap(m_data, other.m_data);
|
||||
swap(m_size, other.m_size);
|
||||
}
|
||||
|
||||
/// Return pointer to data.
|
||||
constexpr const char* data() const noexcept {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
/// Return length of data in bytes.
|
||||
constexpr std::size_t size() const noexcept {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
/// Returns true if size is 0.
|
||||
constexpr bool empty() const noexcept {
|
||||
return m_size == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert data view to string.
|
||||
*
|
||||
* @pre Must not be default constructed data_view.
|
||||
*/
|
||||
std::string to_string() const {
|
||||
protozero_assert(m_data);
|
||||
return std::string{m_data, m_size};
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert data view to string.
|
||||
*
|
||||
* @pre Must not be default constructed data_view.
|
||||
*/
|
||||
explicit operator std::string() const {
|
||||
protozero_assert(m_data);
|
||||
return std::string{m_data, m_size};
|
||||
}
|
||||
|
||||
}; // class data_view
|
||||
|
||||
/**
|
||||
* Swap two data_view objects.
|
||||
*
|
||||
* @param lhs First object.
|
||||
* @param rhs Second object.
|
||||
*/
|
||||
inline void swap(data_view& lhs, data_view& rhs) noexcept {
|
||||
lhs.swap(rhs);
|
||||
}
|
||||
|
||||
/**
|
||||
* Two data_view instances are equal if they have the same size and the
|
||||
* same content.
|
||||
*
|
||||
* @param lhs First object.
|
||||
* @param rhs Second object.
|
||||
*/
|
||||
inline bool operator==(const data_view& lhs, const data_view& rhs) noexcept {
|
||||
return lhs.size() == rhs.size() && std::equal(lhs.data(), lhs.data() + lhs.size(), rhs.data());
|
||||
}
|
||||
|
||||
/**
|
||||
* Two data_view instances are not equal if they have different sizes or the
|
||||
* content differs.
|
||||
*
|
||||
* @param lhs First object.
|
||||
* @param rhs Second object.
|
||||
*/
|
||||
inline bool operator!=(const data_view& lhs, const data_view& rhs) noexcept {
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
} // end namespace protozero
|
||||
|
||||
|
@ -23,13 +23,54 @@ documentation.
|
||||
namespace protozero {
|
||||
|
||||
/**
|
||||
* The maximum length of a 64bit varint.
|
||||
* The maximum length of a 64 bit varint.
|
||||
*/
|
||||
constexpr const int8_t max_varint_length = sizeof(uint64_t) * 8 / 7 + 1;
|
||||
|
||||
// from https://github.com/facebook/folly/blob/master/folly/Varint.h
|
||||
namespace detail {
|
||||
|
||||
// from https://github.com/facebook/folly/blob/master/folly/Varint.h
|
||||
inline uint64_t decode_varint_impl(const char** data, const char* end) {
|
||||
const int8_t* begin = reinterpret_cast<const int8_t*>(*data);
|
||||
const int8_t* iend = reinterpret_cast<const int8_t*>(end);
|
||||
const int8_t* p = begin;
|
||||
uint64_t val = 0;
|
||||
|
||||
if (iend - begin >= max_varint_length) { // fast path
|
||||
do {
|
||||
int64_t b;
|
||||
b = *p++; val = uint64_t((b & 0x7f) ); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 7); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 14); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 21); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 28); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 35); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 42); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 49); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 56); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 63); if (b >= 0) break;
|
||||
throw varint_too_long_exception();
|
||||
} while (false);
|
||||
} else {
|
||||
int shift = 0;
|
||||
while (p != iend && *p < 0) {
|
||||
val |= uint64_t(*p++ & 0x7f) << shift;
|
||||
shift += 7;
|
||||
}
|
||||
if (p == iend) {
|
||||
throw end_of_buffer_exception();
|
||||
}
|
||||
val |= uint64_t(*p++) << shift;
|
||||
}
|
||||
|
||||
*data = reinterpret_cast<const char*>(p);
|
||||
return val;
|
||||
}
|
||||
|
||||
} // end namespace detail
|
||||
|
||||
/**
|
||||
* Decode a 64bit varint.
|
||||
* Decode a 64 bit varint.
|
||||
*
|
||||
* Strong exception guarantee: if there is an exception the data pointer will
|
||||
* not be changed.
|
||||
@ -39,55 +80,69 @@ constexpr const int8_t max_varint_length = sizeof(uint64_t) * 8 / 7 + 1;
|
||||
* @param[in] end Pointer one past the end of the input data.
|
||||
* @returns The decoded integer
|
||||
* @throws varint_too_long_exception if the varint is longer then the maximum
|
||||
* length that would fit in a 64bit int. Usually this means your data
|
||||
* length that would fit in a 64 bit int. Usually this means your data
|
||||
* is corrupted or you are trying to read something as a varint that
|
||||
* isn't.
|
||||
* @throws end_of_buffer_exception if the *end* of the buffer was reached
|
||||
* before the end of the varint.
|
||||
*/
|
||||
inline uint64_t decode_varint(const char** data, const char* end) {
|
||||
const int8_t* begin = reinterpret_cast<const int8_t*>(*data);
|
||||
const int8_t* iend = reinterpret_cast<const int8_t*>(end);
|
||||
const int8_t* p = begin;
|
||||
uint64_t val = 0;
|
||||
|
||||
if (iend - begin >= max_varint_length) { // fast path
|
||||
do {
|
||||
int64_t b;
|
||||
b = *p++; val = uint64_t((b & 0x7f) ); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 7); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 14); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 21); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 28); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 35); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 42); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 49); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 56); if (b >= 0) break;
|
||||
b = *p++; val |= uint64_t((b & 0x7f) << 63); if (b >= 0) break;
|
||||
throw varint_too_long_exception();
|
||||
} while (false);
|
||||
} else {
|
||||
int shift = 0;
|
||||
while (p != iend && *p < 0) {
|
||||
val |= uint64_t(*p++ & 0x7f) << shift;
|
||||
shift += 7;
|
||||
}
|
||||
if (p == iend) {
|
||||
throw end_of_buffer_exception();
|
||||
}
|
||||
val |= uint64_t(*p++) << shift;
|
||||
// If this is a one-byte varint, decode it here.
|
||||
if (end != *data && ((**data & 0x80) == 0)) {
|
||||
uint64_t val = uint64_t(**data);
|
||||
++(*data);
|
||||
return val;
|
||||
}
|
||||
|
||||
*data = reinterpret_cast<const char*>(p);
|
||||
return val;
|
||||
// If this varint is more than one byte, defer to complete implementation.
|
||||
return detail::decode_varint_impl(data, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Varint-encode a 64bit integer.
|
||||
* Skip over a varint.
|
||||
*
|
||||
* Strong exception guarantee: if there is an exception the data pointer will
|
||||
* not be changed.
|
||||
*
|
||||
* @param[in,out] data Pointer to pointer to the input data. After the function
|
||||
* returns this will point to the next data to be read.
|
||||
* @param[in] end Pointer one past the end of the input data.
|
||||
* @throws end_of_buffer_exception if the *end* of the buffer was reached
|
||||
* before the end of the varint.
|
||||
*/
|
||||
template <typename OutputIterator>
|
||||
inline int write_varint(OutputIterator data, uint64_t value) {
|
||||
int n=1;
|
||||
inline void skip_varint(const char** data, const char* end) {
|
||||
const int8_t* begin = reinterpret_cast<const int8_t*>(*data);
|
||||
const int8_t* iend = reinterpret_cast<const int8_t*>(end);
|
||||
const int8_t* p = begin;
|
||||
|
||||
while (p != iend && *p < 0) {
|
||||
++p;
|
||||
}
|
||||
|
||||
if (p >= begin + max_varint_length) {
|
||||
throw varint_too_long_exception();
|
||||
}
|
||||
|
||||
if (p == iend) {
|
||||
throw end_of_buffer_exception();
|
||||
}
|
||||
|
||||
++p;
|
||||
|
||||
*data = reinterpret_cast<const char*>(p);
|
||||
}
|
||||
|
||||
/**
|
||||
* Varint encode a 64 bit integer.
|
||||
*
|
||||
* @tparam T An output iterator type.
|
||||
* @param data Output iterator the varint encoded value will be written to
|
||||
* byte by byte.
|
||||
* @param value The integer that will be encoded.
|
||||
* @throws Any exception thrown by increment or dereference operator on data.
|
||||
*/
|
||||
template <typename T>
|
||||
inline int write_varint(T data, uint64_t value) {
|
||||
int n = 1;
|
||||
|
||||
while (value >= 0x80) {
|
||||
*data++ = char((value & 0x7f) | 0x80);
|
||||
@ -102,29 +157,29 @@ inline int write_varint(OutputIterator data, uint64_t value) {
|
||||
/**
|
||||
* ZigZag encodes a 32 bit integer.
|
||||
*/
|
||||
inline uint32_t encode_zigzag32(int32_t value) noexcept {
|
||||
inline constexpr uint32_t encode_zigzag32(int32_t value) noexcept {
|
||||
return (static_cast<uint32_t>(value) << 1) ^ (static_cast<uint32_t>(value >> 31));
|
||||
}
|
||||
|
||||
/**
|
||||
* ZigZag encodes a 64 bit integer.
|
||||
*/
|
||||
inline uint64_t encode_zigzag64(int64_t value) noexcept {
|
||||
inline constexpr uint64_t encode_zigzag64(int64_t value) noexcept {
|
||||
return (static_cast<uint64_t>(value) << 1) ^ (static_cast<uint64_t>(value >> 63));
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a 32 bit ZigZag-encoded integer.
|
||||
*/
|
||||
inline int32_t decode_zigzag32(uint32_t value) noexcept {
|
||||
return int32_t(value >> 1) ^ -int32_t(value & 1);
|
||||
inline constexpr int32_t decode_zigzag32(uint32_t value) noexcept {
|
||||
return static_cast<int32_t>(value >> 1) ^ -static_cast<int32_t>(value & 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a 64 bit ZigZag-encoded integer.
|
||||
*/
|
||||
inline int64_t decode_zigzag64(uint64_t value) noexcept {
|
||||
return int64_t(value >> 1) ^ -int64_t(value & 1);
|
||||
inline constexpr int64_t decode_zigzag64(uint64_t value) noexcept {
|
||||
return static_cast<int64_t>(value >> 1) ^ -static_cast<int64_t>(value & 1);
|
||||
}
|
||||
|
||||
} // end namespace protozero
|
||||
|
@ -10,13 +10,25 @@ documentation.
|
||||
|
||||
*****************************************************************************/
|
||||
|
||||
#define PROTOZERO_VERSION_MAJOR 1
|
||||
#define PROTOZERO_VERSION_MINOR 3
|
||||
#define PROTOZERO_VERSION_PATCH 0
|
||||
/**
|
||||
* @file version.hpp
|
||||
*
|
||||
* @brief Contains macros defining the protozero version.
|
||||
*/
|
||||
|
||||
/// The major version number
|
||||
#define PROTOZERO_VERSION_MAJOR 1
|
||||
|
||||
/// The minor version number
|
||||
#define PROTOZERO_VERSION_MINOR 5
|
||||
|
||||
/// The patch number
|
||||
#define PROTOZERO_VERSION_PATCH 2
|
||||
|
||||
/// The complete version number
|
||||
#define PROTOZERO_VERSION_CODE (PROTOZERO_VERSION_MAJOR * 10000 + PROTOZERO_VERSION_MINOR * 100 + PROTOZERO_VERSION_PATCH)
|
||||
|
||||
#define PROTOZERO_VERSION_STRING "1.3.0"
|
||||
|
||||
/// Version number as string
|
||||
#define PROTOZERO_VERSION_STRING "1.5.2"
|
||||
|
||||
#endif // PROTOZERO_VERSION_HPP
|
||||
|
185
read_json.cpp
Normal file
185
read_json.cpp
Normal file
@ -0,0 +1,185 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <math.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "jsonpull/jsonpull.h"
|
||||
#include "geometry.hpp"
|
||||
#include "projection.hpp"
|
||||
#include "read_json.hpp"
|
||||
#include "text.hpp"
|
||||
#include "mvt.hpp"
|
||||
#include "milo/dtoa_milo.h"
|
||||
|
||||
const char *geometry_names[GEOM_TYPES] = {
|
||||
"Point", "MultiPoint", "LineString", "MultiLineString", "Polygon", "MultiPolygon",
|
||||
};
|
||||
|
||||
int geometry_within[GEOM_TYPES] = {
|
||||
-1, /* point */
|
||||
GEOM_POINT, /* multipoint */
|
||||
GEOM_POINT, /* linestring */
|
||||
GEOM_LINESTRING, /* multilinestring */
|
||||
GEOM_LINESTRING, /* polygon */
|
||||
GEOM_POLYGON, /* multipolygon */
|
||||
};
|
||||
|
||||
int mb_geometry[GEOM_TYPES] = {
|
||||
VT_POINT, VT_POINT, VT_LINE, VT_LINE, VT_POLYGON, VT_POLYGON,
|
||||
};
|
||||
|
||||
void json_context(json_object *j) {
|
||||
char *s = json_stringify(j);
|
||||
|
||||
if (strlen(s) >= 500) {
|
||||
sprintf(s + 497, "...");
|
||||
}
|
||||
|
||||
fprintf(stderr, "In JSON object %s\n", s);
|
||||
free(s); // stringify
|
||||
}
|
||||
|
||||
void parse_geometry(int t, json_object *j, drawvec &out, int op, const char *fname, int line, json_object *feature) {
|
||||
if (j == NULL || j->type != JSON_ARRAY) {
|
||||
fprintf(stderr, "%s:%d: expected array for type %d\n", fname, line, t);
|
||||
json_context(feature);
|
||||
return;
|
||||
}
|
||||
|
||||
int within = geometry_within[t];
|
||||
if (within >= 0) {
|
||||
size_t i;
|
||||
for (i = 0; i < j->length; i++) {
|
||||
if (within == GEOM_POINT) {
|
||||
if (i == 0 || mb_geometry[t] == VT_POINT) {
|
||||
op = VT_MOVETO;
|
||||
} else {
|
||||
op = VT_LINETO;
|
||||
}
|
||||
}
|
||||
|
||||
parse_geometry(within, j->array[i], out, op, fname, line, feature);
|
||||
}
|
||||
} else {
|
||||
if (j->length >= 2 && j->array[0]->type == JSON_NUMBER && j->array[1]->type == JSON_NUMBER) {
|
||||
long long x, y;
|
||||
double lon = j->array[0]->number;
|
||||
double lat = j->array[1]->number;
|
||||
projection->project(lon, lat, 32, &x, &y);
|
||||
|
||||
if (j->length > 2) {
|
||||
static int warned = 0;
|
||||
|
||||
if (!warned) {
|
||||
fprintf(stderr, "%s:%d: ignoring dimensions beyond two\n", fname, line);
|
||||
json_context(j);
|
||||
json_context(feature);
|
||||
warned = 1;
|
||||
}
|
||||
}
|
||||
|
||||
out.push_back(draw(op, x, y));
|
||||
} else {
|
||||
fprintf(stderr, "%s:%d: malformed point\n", fname, line);
|
||||
json_context(j);
|
||||
json_context(feature);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
if (t == GEOM_POLYGON) {
|
||||
// Note that this is not using the correct meaning of closepath.
|
||||
//
|
||||
// We are using it here to close an entire Polygon, to distinguish
|
||||
// the Polygons within a MultiPolygon from each other.
|
||||
//
|
||||
// This will be undone in fix_polygon(), which needs to know which
|
||||
// rings come from which Polygons so that it can make the winding order
|
||||
// of the outer ring be the opposite of the order of the inner rings.
|
||||
|
||||
out.push_back(draw(VT_CLOSEPATH, 0, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void canonicalize(json_object *o) {
|
||||
if (o->type == JSON_NUMBER) {
|
||||
std::string s;
|
||||
long long v;
|
||||
unsigned long long uv;
|
||||
|
||||
if (is_integer(o->string, &v)) {
|
||||
s = std::to_string(v);
|
||||
} else if (is_unsigned_integer(o->string, &uv)) {
|
||||
s = std::to_string(uv);
|
||||
} else {
|
||||
s = milo::dtoa_milo(o->number);
|
||||
}
|
||||
free(o->string);
|
||||
o->string = strdup(s.c_str());
|
||||
} else if (o->type == JSON_HASH) {
|
||||
for (size_t i = 0; i < o->length; i++) {
|
||||
canonicalize(o->values[i]);
|
||||
}
|
||||
} else if (o->type == JSON_ARRAY) {
|
||||
for (size_t i = 0; i < o->length; i++) {
|
||||
canonicalize(o->array[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stringify_value(json_object *value, int &type, std::string &stringified, const char *reading, int line, json_object *feature) {
|
||||
if (value != NULL) {
|
||||
int vt = value->type;
|
||||
std::string val;
|
||||
|
||||
if (vt == JSON_STRING || vt == JSON_NUMBER) {
|
||||
val = value->string;
|
||||
} else if (vt == JSON_TRUE) {
|
||||
val = "true";
|
||||
} else if (vt == JSON_FALSE) {
|
||||
val = "false";
|
||||
} else if (vt == JSON_NULL) {
|
||||
val = "null";
|
||||
} else {
|
||||
canonicalize(value);
|
||||
const char *v = json_stringify(value);
|
||||
val = std::string(v);
|
||||
free((void *) v); // stringify
|
||||
}
|
||||
|
||||
if (vt == JSON_STRING) {
|
||||
type = mvt_string;
|
||||
stringified = val;
|
||||
std::string err = check_utf8(val);
|
||||
if (err != "") {
|
||||
fprintf(stderr, "%s:%d: %s\n", reading, line, err.c_str());
|
||||
json_context(feature);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else if (vt == JSON_NUMBER) {
|
||||
type = mvt_double;
|
||||
|
||||
long long v;
|
||||
unsigned long long uv;
|
||||
|
||||
if (is_integer(value->string, &v)) {
|
||||
stringified = std::to_string(v);
|
||||
} else if (is_unsigned_integer(value->string, &uv)) {
|
||||
stringified = std::to_string(uv);
|
||||
} else {
|
||||
stringified = milo::dtoa_milo(value->number);
|
||||
}
|
||||
} else if (vt == JSON_TRUE || vt == JSON_FALSE) {
|
||||
type = mvt_bool;
|
||||
stringified = val;
|
||||
} else if (vt == JSON_NULL) {
|
||||
type = mvt_null;
|
||||
stringified = "null";
|
||||
} else {
|
||||
type = mvt_string;
|
||||
stringified = val;
|
||||
}
|
||||
}
|
||||
}
|
16
read_json.hpp
Normal file
16
read_json.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
#define GEOM_POINT 0 /* array of positions */
|
||||
#define GEOM_MULTIPOINT 1 /* array of arrays of positions */
|
||||
#define GEOM_LINESTRING 2 /* array of arrays of positions */
|
||||
#define GEOM_MULTILINESTRING 3 /* array of arrays of arrays of positions */
|
||||
#define GEOM_POLYGON 4 /* array of arrays of arrays of positions */
|
||||
#define GEOM_MULTIPOLYGON 5 /* array of arrays of arrays of arrays of positions */
|
||||
#define GEOM_TYPES 6
|
||||
|
||||
extern const char *geometry_names[GEOM_TYPES];
|
||||
extern int geometry_within[GEOM_TYPES];
|
||||
extern int mb_geometry[GEOM_TYPES];
|
||||
|
||||
void json_context(json_object *j);
|
||||
void parse_geometry(int t, json_object *j, drawvec &out, int op, const char *fname, int line, json_object *feature);
|
||||
|
||||
void stringify_value(json_object *value, int &type, std::string &stringified, const char *reading, int line, json_object *feature);
|
554
serial.cpp
554
serial.cpp
@ -7,11 +7,24 @@
|
||||
#include <sqlite3.h>
|
||||
#include <set>
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
#include <limits.h>
|
||||
#include "protozero/varint.hpp"
|
||||
#include "geometry.hpp"
|
||||
#include "mbtiles.hpp"
|
||||
#include "tile.hpp"
|
||||
#include "serial.hpp"
|
||||
#include "options.hpp"
|
||||
#include "main.hpp"
|
||||
#include "pool.hpp"
|
||||
#include "projection.hpp"
|
||||
#include "evaluator.hpp"
|
||||
#include "milo/dtoa_milo.h"
|
||||
|
||||
// Offset coordinates to keep them positive
|
||||
#define COORD_OFFSET (4LL << 32)
|
||||
#define SHIFT_RIGHT(a) ((((a) + COORD_OFFSET) >> geometry_scale) - (COORD_OFFSET >> geometry_scale))
|
||||
#define SHIFT_LEFT(a) ((((a) + (COORD_OFFSET >> geometry_scale)) << geometry_scale) - COORD_OFFSET)
|
||||
|
||||
size_t fwrite_check(const void *ptr, size_t size, size_t nitems, FILE *stream, const char *fname) {
|
||||
size_t w = fwrite(ptr, size, nitems, stream);
|
||||
@ -22,17 +35,17 @@ size_t fwrite_check(const void *ptr, size_t size, size_t nitems, FILE *stream, c
|
||||
return w;
|
||||
}
|
||||
|
||||
void serialize_int(FILE *out, int n, long long *fpos, const char *fname) {
|
||||
void serialize_int(FILE *out, int n, std::atomic<long long> *fpos, const char *fname) {
|
||||
serialize_long_long(out, n, fpos, fname);
|
||||
}
|
||||
|
||||
void serialize_long_long(FILE *out, long long n, long long *fpos, const char *fname) {
|
||||
void serialize_long_long(FILE *out, long long n, std::atomic<long long> *fpos, const char *fname) {
|
||||
unsigned long long zigzag = protozero::encode_zigzag64(n);
|
||||
|
||||
serialize_ulong_long(out, zigzag, fpos, fname);
|
||||
}
|
||||
|
||||
void serialize_ulong_long(FILE *out, unsigned long long zigzag, long long *fpos, const char *fname) {
|
||||
void serialize_ulong_long(FILE *out, unsigned long long zigzag, std::atomic<long long> *fpos, const char *fname) {
|
||||
while (1) {
|
||||
unsigned char b = zigzag & 0x7F;
|
||||
if ((zigzag >> 7) != 0) {
|
||||
@ -54,12 +67,12 @@ void serialize_ulong_long(FILE *out, unsigned long long zigzag, long long *fpos,
|
||||
}
|
||||
}
|
||||
|
||||
void serialize_byte(FILE *out, signed char n, long long *fpos, const char *fname) {
|
||||
void serialize_byte(FILE *out, signed char n, std::atomic<long long> *fpos, const char *fname) {
|
||||
fwrite_check(&n, sizeof(signed char), 1, out, fname);
|
||||
*fpos += sizeof(signed char);
|
||||
}
|
||||
|
||||
void serialize_uint(FILE *out, unsigned n, long long *fpos, const char *fname) {
|
||||
void serialize_uint(FILE *out, unsigned n, std::atomic<long long> *fpos, const char *fname) {
|
||||
fwrite_check(&n, sizeof(unsigned), 1, out, fname);
|
||||
*fpos += sizeof(unsigned);
|
||||
}
|
||||
@ -104,14 +117,14 @@ void deserialize_byte(char **f, signed char *n) {
|
||||
*f += sizeof(signed char);
|
||||
}
|
||||
|
||||
int deserialize_long_long_io(FILE *f, long long *n, long long *geompos) {
|
||||
int deserialize_long_long_io(FILE *f, long long *n, std::atomic<long long> *geompos) {
|
||||
unsigned long long zigzag = 0;
|
||||
int ret = deserialize_ulong_long_io(f, &zigzag, geompos);
|
||||
*n = protozero::decode_zigzag64(zigzag);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int deserialize_ulong_long_io(FILE *f, unsigned long long *zigzag, long long *geompos) {
|
||||
int deserialize_ulong_long_io(FILE *f, unsigned long long *zigzag, std::atomic<long long> *geompos) {
|
||||
*zigzag = 0;
|
||||
int shift = 0;
|
||||
|
||||
@ -135,14 +148,14 @@ int deserialize_ulong_long_io(FILE *f, unsigned long long *zigzag, long long *ge
|
||||
return 1;
|
||||
}
|
||||
|
||||
int deserialize_int_io(FILE *f, int *n, long long *geompos) {
|
||||
int deserialize_int_io(FILE *f, int *n, std::atomic<long long> *geompos) {
|
||||
long long ll = 0;
|
||||
int ret = deserialize_long_long_io(f, &ll, geompos);
|
||||
*n = ll;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int deserialize_uint_io(FILE *f, unsigned *n, long long *geompos) {
|
||||
int deserialize_uint_io(FILE *f, unsigned *n, std::atomic<long long> *geompos) {
|
||||
if (fread(n, sizeof(unsigned), 1, f) != 1) {
|
||||
return 0;
|
||||
}
|
||||
@ -150,7 +163,7 @@ int deserialize_uint_io(FILE *f, unsigned *n, long long *geompos) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int deserialize_byte_io(FILE *f, signed char *n, long long *geompos) {
|
||||
int deserialize_byte_io(FILE *f, signed char *n, std::atomic<long long> *geompos) {
|
||||
int c = getc(f);
|
||||
if (c == EOF) {
|
||||
return 0;
|
||||
@ -160,7 +173,7 @@ int deserialize_byte_io(FILE *f, signed char *n, long long *geompos) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void write_geometry(drawvec const &dv, long long *fpos, FILE *out, const char *fname, long long wx, long long wy) {
|
||||
static void write_geometry(drawvec const &dv, std::atomic<long long> *fpos, FILE *out, const char *fname, long long wx, long long wy) {
|
||||
for (size_t i = 0; i < dv.size(); i++) {
|
||||
if (dv[i].op == VT_MOVETO || dv[i].op == VT_LINETO) {
|
||||
serialize_byte(out, dv[i].op, fpos, fname);
|
||||
@ -174,7 +187,8 @@ static void write_geometry(drawvec const &dv, long long *fpos, FILE *out, const
|
||||
}
|
||||
}
|
||||
|
||||
void serialize_feature(FILE *geomfile, serial_feature *sf, long long *geompos, const char *fname, long long wx, long long wy, bool include_minzoom) {
|
||||
// called from generating the next zoom level
|
||||
void serialize_feature(FILE *geomfile, serial_feature *sf, std::atomic<long long> *geompos, const char *fname, long long wx, long long wy, bool include_minzoom) {
|
||||
serialize_byte(geomfile, sf->t, geompos, fname);
|
||||
|
||||
long long layer = 0;
|
||||
@ -211,22 +225,516 @@ void serialize_feature(FILE *geomfile, serial_feature *sf, long long *geompos, c
|
||||
serialize_long_long(geomfile, sf->extent, geompos, fname);
|
||||
}
|
||||
|
||||
serialize_int(geomfile, sf->m, geompos, fname);
|
||||
if (sf->m != 0) {
|
||||
serialize_long_long(geomfile, sf->metapos, geompos, fname);
|
||||
}
|
||||
serialize_long_long(geomfile, sf->metapos, geompos, fname);
|
||||
|
||||
if (sf->metapos < 0 && sf->m != sf->keys.size()) {
|
||||
fprintf(stderr, "Internal error: %lld doesn't match %lld\n", (long long) sf->m, (long long) sf->keys.size());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (sf->metapos < 0) {
|
||||
serialize_long_long(geomfile, sf->keys.size(), geompos, fname);
|
||||
|
||||
for (size_t i = 0; i < sf->keys.size(); i++) {
|
||||
serialize_long_long(geomfile, sf->keys[i], geompos, fname);
|
||||
serialize_long_long(geomfile, sf->values[i], geompos, fname);
|
||||
for (size_t i = 0; i < sf->keys.size(); i++) {
|
||||
serialize_long_long(geomfile, sf->keys[i], geompos, fname);
|
||||
serialize_long_long(geomfile, sf->values[i], geompos, fname);
|
||||
}
|
||||
}
|
||||
|
||||
if (include_minzoom) {
|
||||
serialize_byte(geomfile, sf->feature_minzoom, geompos, fname);
|
||||
}
|
||||
}
|
||||
|
||||
serial_feature deserialize_feature(FILE *geoms, std::atomic<long long> *geompos_in, char *metabase, long long *meta_off, unsigned z, unsigned tx, unsigned ty, unsigned *initial_x, unsigned *initial_y) {
|
||||
serial_feature sf;
|
||||
|
||||
deserialize_byte_io(geoms, &sf.t, geompos_in);
|
||||
if (sf.t < 0) {
|
||||
return sf;
|
||||
}
|
||||
|
||||
deserialize_long_long_io(geoms, &sf.layer, geompos_in);
|
||||
|
||||
sf.seq = 0;
|
||||
if (sf.layer & (1 << 5)) {
|
||||
deserialize_long_long_io(geoms, &sf.seq, geompos_in);
|
||||
}
|
||||
|
||||
sf.tippecanoe_minzoom = -1;
|
||||
sf.tippecanoe_maxzoom = -1;
|
||||
sf.id = 0;
|
||||
sf.has_id = false;
|
||||
if (sf.layer & (1 << 1)) {
|
||||
deserialize_int_io(geoms, &sf.tippecanoe_minzoom, geompos_in);
|
||||
}
|
||||
if (sf.layer & (1 << 0)) {
|
||||
deserialize_int_io(geoms, &sf.tippecanoe_maxzoom, geompos_in);
|
||||
}
|
||||
if (sf.layer & (1 << 2)) {
|
||||
sf.has_id = true;
|
||||
deserialize_ulong_long_io(geoms, &sf.id, geompos_in);
|
||||
}
|
||||
|
||||
deserialize_int_io(geoms, &sf.segment, geompos_in);
|
||||
|
||||
sf.index = 0;
|
||||
sf.extent = 0;
|
||||
|
||||
sf.geometry = decode_geometry(geoms, geompos_in, z, tx, ty, sf.bbox, initial_x[sf.segment], initial_y[sf.segment]);
|
||||
if (sf.layer & (1 << 4)) {
|
||||
deserialize_ulong_long_io(geoms, &sf.index, geompos_in);
|
||||
}
|
||||
if (sf.layer & (1 << 3)) {
|
||||
deserialize_long_long_io(geoms, &sf.extent, geompos_in);
|
||||
}
|
||||
|
||||
sf.layer >>= 6;
|
||||
|
||||
sf.metapos = 0;
|
||||
deserialize_long_long_io(geoms, &sf.metapos, geompos_in);
|
||||
|
||||
if (sf.metapos >= 0) {
|
||||
char *meta = metabase + sf.metapos + meta_off[sf.segment];
|
||||
long long count;
|
||||
deserialize_long_long(&meta, &count);
|
||||
|
||||
for (long long i = 0; i < count; i++) {
|
||||
long long k, v;
|
||||
deserialize_long_long(&meta, &k);
|
||||
deserialize_long_long(&meta, &v);
|
||||
sf.keys.push_back(k);
|
||||
sf.values.push_back(v);
|
||||
}
|
||||
} else {
|
||||
long long count;
|
||||
deserialize_long_long_io(geoms, &count, geompos_in);
|
||||
|
||||
for (long long i = 0; i < count; i++) {
|
||||
long long k, v;
|
||||
deserialize_long_long_io(geoms, &k, geompos_in);
|
||||
deserialize_long_long_io(geoms, &v, geompos_in);
|
||||
sf.keys.push_back(k);
|
||||
sf.values.push_back(v);
|
||||
}
|
||||
}
|
||||
|
||||
deserialize_byte_io(geoms, &sf.feature_minzoom, geompos_in);
|
||||
|
||||
return sf;
|
||||
}
|
||||
|
||||
static long long scale_geometry(struct serialization_state *sst, long long *bbox, drawvec &geom) {
|
||||
long long offset = 0;
|
||||
long long prev = 0;
|
||||
bool has_prev = false;
|
||||
double scale = 1.0 / (1 << geometry_scale);
|
||||
|
||||
for (size_t i = 0; i < geom.size(); i++) {
|
||||
if (geom[i].op == VT_MOVETO || geom[i].op == VT_LINETO) {
|
||||
long long x = geom[i].x;
|
||||
long long y = geom[i].y;
|
||||
|
||||
if (additional[A_DETECT_WRAPAROUND]) {
|
||||
x += offset;
|
||||
if (has_prev) {
|
||||
if (x - prev > (1LL << 31)) {
|
||||
offset -= 1LL << 32;
|
||||
x -= 1LL << 32;
|
||||
} else if (prev - x > (1LL << 31)) {
|
||||
offset += 1LL << 32;
|
||||
x += 1LL << 32;
|
||||
}
|
||||
}
|
||||
|
||||
has_prev = true;
|
||||
prev = x;
|
||||
}
|
||||
|
||||
if (x < bbox[0]) {
|
||||
bbox[0] = x;
|
||||
}
|
||||
if (y < bbox[1]) {
|
||||
bbox[1] = y;
|
||||
}
|
||||
if (x > bbox[2]) {
|
||||
bbox[2] = x;
|
||||
}
|
||||
if (y > bbox[3]) {
|
||||
bbox[3] = y;
|
||||
}
|
||||
|
||||
if (!*(sst->initialized)) {
|
||||
if (x < 0 || x >= (1LL << 32) || y < 0 || y >= (1LL << 32)) {
|
||||
*(sst->initial_x) = 1LL << 31;
|
||||
*(sst->initial_y) = 1LL << 31;
|
||||
} else {
|
||||
*(sst->initial_x) = (((x + COORD_OFFSET) >> geometry_scale) << geometry_scale) - COORD_OFFSET;
|
||||
*(sst->initial_y) = (((y + COORD_OFFSET) >> geometry_scale) << geometry_scale) - COORD_OFFSET;
|
||||
}
|
||||
|
||||
*(sst->initialized) = 1;
|
||||
}
|
||||
|
||||
if (additional[A_GRID_LOW_ZOOMS]) {
|
||||
// If we are gridding, snap to the maxzoom grid in case the incoming data
|
||||
// is already supposed to be aligned to tile boundaries (but is not, exactly,
|
||||
// because of rounding error during projection).
|
||||
|
||||
geom[i].x = std::round(x * scale);
|
||||
geom[i].y = std::round(y * scale);
|
||||
} else {
|
||||
geom[i].x = SHIFT_RIGHT(x);
|
||||
geom[i].y = SHIFT_RIGHT(y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return geom.size();
|
||||
}
|
||||
|
||||
static std::string strip_zeroes(std::string s) {
|
||||
// Doesn't do anything special with '-' followed by leading zeros
|
||||
// since integer IDs must be positive
|
||||
|
||||
while (s.size() > 0 && s[0] == '0') {
|
||||
s.erase(s.begin());
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// called from frontends
|
||||
int serialize_feature(struct serialization_state *sst, serial_feature &sf) {
|
||||
struct reader *r = &(*sst->readers)[sst->segment];
|
||||
|
||||
sf.bbox[0] = LLONG_MAX;
|
||||
sf.bbox[1] = LLONG_MAX;
|
||||
sf.bbox[2] = LLONG_MIN;
|
||||
sf.bbox[3] = LLONG_MIN;
|
||||
scale_geometry(sst, sf.bbox, sf.geometry);
|
||||
|
||||
// This has to happen after scaling so that the wraparound detection has happened first.
|
||||
// Otherwise the inner/outer calculation will be confused by bad geometries.
|
||||
if (sf.t == VT_POLYGON) {
|
||||
sf.geometry = fix_polygon(sf.geometry);
|
||||
}
|
||||
|
||||
for (auto &c : clipbboxes) {
|
||||
if (sf.t == VT_POLYGON) {
|
||||
sf.geometry = simple_clip_poly(sf.geometry, SHIFT_RIGHT(c.minx), SHIFT_RIGHT(c.miny), SHIFT_RIGHT(c.maxx), SHIFT_RIGHT(c.maxy));
|
||||
} else if (sf.t == VT_LINE) {
|
||||
sf.geometry = clip_lines(sf.geometry, SHIFT_RIGHT(c.minx), SHIFT_RIGHT(c.miny), SHIFT_RIGHT(c.maxx), SHIFT_RIGHT(c.maxy));
|
||||
sf.geometry = remove_noop(sf.geometry, sf.t, 0);
|
||||
} else if (sf.t == VT_POINT) {
|
||||
sf.geometry = clip_point(sf.geometry, SHIFT_RIGHT(c.minx), SHIFT_RIGHT(c.miny), SHIFT_RIGHT(c.maxx), SHIFT_RIGHT(c.maxy));
|
||||
}
|
||||
|
||||
sf.bbox[0] = LLONG_MAX;
|
||||
sf.bbox[1] = LLONG_MAX;
|
||||
sf.bbox[2] = LLONG_MIN;
|
||||
sf.bbox[3] = LLONG_MIN;
|
||||
|
||||
for (auto &g : sf.geometry) {
|
||||
long long x = SHIFT_LEFT(g.x);
|
||||
long long y = SHIFT_LEFT(g.y);
|
||||
|
||||
if (x < sf.bbox[0]) {
|
||||
sf.bbox[0] = x;
|
||||
}
|
||||
if (y < sf.bbox[1]) {
|
||||
sf.bbox[1] = y;
|
||||
}
|
||||
if (x > sf.bbox[2]) {
|
||||
sf.bbox[2] = x;
|
||||
}
|
||||
if (y > sf.bbox[3]) {
|
||||
sf.bbox[3] = y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sf.geometry.size() == 0) {
|
||||
// Feature was clipped away
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!sf.has_id) {
|
||||
if (additional[A_GENERATE_IDS]) {
|
||||
sf.has_id = true;
|
||||
sf.id = sf.seq + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (sst->want_dist) {
|
||||
std::vector<unsigned long long> locs;
|
||||
for (size_t i = 0; i < sf.geometry.size(); i++) {
|
||||
if (sf.geometry[i].op == VT_MOVETO || sf.geometry[i].op == VT_LINETO) {
|
||||
locs.push_back(encode_index(SHIFT_LEFT(sf.geometry[i].x), SHIFT_LEFT(sf.geometry[i].y)));
|
||||
}
|
||||
}
|
||||
std::sort(locs.begin(), locs.end());
|
||||
size_t n = 0;
|
||||
double sum = 0;
|
||||
for (size_t i = 1; i < locs.size(); i++) {
|
||||
if (locs[i - 1] != locs[i]) {
|
||||
sum += log(locs[i] - locs[i - 1]);
|
||||
n++;
|
||||
}
|
||||
}
|
||||
if (n > 0) {
|
||||
double avg = exp(sum / n);
|
||||
// Convert approximately from tile units to feet
|
||||
double dist_ft = sqrt(avg) / 33;
|
||||
|
||||
*(sst->dist_sum) += log(dist_ft) * n;
|
||||
*(sst->dist_count) += n;
|
||||
}
|
||||
locs.clear();
|
||||
}
|
||||
|
||||
bool inline_meta = true;
|
||||
// Don't inline metadata for features that will span several tiles at maxzoom
|
||||
if (sf.geometry.size() > 0 && (sf.bbox[2] < sf.bbox[0] || sf.bbox[3] < sf.bbox[1])) {
|
||||
fprintf(stderr, "Internal error: impossible feature bounding box %llx,%llx,%llx,%llx\n", sf.bbox[0], sf.bbox[1], sf.bbox[2], sf.bbox[3]);
|
||||
}
|
||||
if (sf.bbox[0] == LLONG_MAX) {
|
||||
// No bounding box (empty geometry)
|
||||
// Shouldn't happen, but avoid arithmetic overflow below
|
||||
} else if (sf.bbox[2] - sf.bbox[0] > (2LL << (32 - sst->maxzoom)) || sf.bbox[3] - sf.bbox[1] > (2LL << (32 - sst->maxzoom))) {
|
||||
inline_meta = false;
|
||||
|
||||
if (prevent[P_CLIPPING]) {
|
||||
static std::atomic<long long> warned(0);
|
||||
long long extent = ((sf.bbox[2] - sf.bbox[0]) / ((1LL << (32 - sst->maxzoom)) + 1)) * ((sf.bbox[3] - sf.bbox[1]) / ((1LL << (32 - sst->maxzoom)) + 1));
|
||||
if (extent > warned) {
|
||||
fprintf(stderr, "Warning: %s:%d: Large unclipped (-pc) feature may be duplicated across %lld tiles\n", sst->fname, sst->line, extent);
|
||||
warned = extent;
|
||||
|
||||
if (extent > 10000) {
|
||||
fprintf(stderr, "Exiting because this can't be right.\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double extent = 0;
|
||||
if (additional[A_DROP_SMALLEST_AS_NEEDED] || additional[A_COALESCE_SMALLEST_AS_NEEDED]) {
|
||||
if (sf.t == VT_POLYGON) {
|
||||
for (size_t i = 0; i < sf.geometry.size(); i++) {
|
||||
if (sf.geometry[i].op == VT_MOVETO) {
|
||||
size_t j;
|
||||
for (j = i + 1; j < sf.geometry.size(); j++) {
|
||||
if (sf.geometry[j].op != VT_LINETO) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
extent += get_area(sf.geometry, i, j);
|
||||
i = j - 1;
|
||||
}
|
||||
}
|
||||
} else if (sf.t == VT_LINE) {
|
||||
for (size_t i = 1; i < sf.geometry.size(); i++) {
|
||||
if (sf.geometry[i].op == VT_LINETO) {
|
||||
double xd = sf.geometry[i].x - sf.geometry[i - 1].x;
|
||||
double yd = sf.geometry[i].y - sf.geometry[i - 1].y;
|
||||
extent += sqrt(xd * xd + yd * yd);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (extent <= LLONG_MAX) {
|
||||
sf.extent = (long long) extent;
|
||||
} else {
|
||||
sf.extent = LLONG_MAX;
|
||||
}
|
||||
|
||||
if (!prevent[P_INPUT_ORDER]) {
|
||||
sf.seq = 0;
|
||||
}
|
||||
|
||||
long long bbox_index;
|
||||
|
||||
// Calculate the center even if off the edge of the plane,
|
||||
// and then mask to bring it back into the addressable area
|
||||
long long midx = (sf.bbox[0] / 2 + sf.bbox[2] / 2) & ((1LL << 32) - 1);
|
||||
long long midy = (sf.bbox[1] / 2 + sf.bbox[3] / 2) & ((1LL << 32) - 1);
|
||||
bbox_index = encode_index(midx, midy);
|
||||
|
||||
if (additional[A_DROP_DENSEST_AS_NEEDED] || additional[A_COALESCE_DENSEST_AS_NEEDED] || additional[A_CLUSTER_DENSEST_AS_NEEDED] || additional[A_CALCULATE_FEATURE_DENSITY] || additional[A_INCREASE_GAMMA_AS_NEEDED] || sst->uses_gamma || cluster_distance != 0) {
|
||||
sf.index = bbox_index;
|
||||
} else {
|
||||
sf.index = 0;
|
||||
}
|
||||
|
||||
if (sst->layermap->count(sf.layername) == 0) {
|
||||
sst->layermap->insert(std::pair<std::string, layermap_entry>(sf.layername, layermap_entry(sst->layermap->size())));
|
||||
}
|
||||
|
||||
auto ai = sst->layermap->find(sf.layername);
|
||||
if (ai != sst->layermap->end()) {
|
||||
sf.layer = ai->second.id;
|
||||
|
||||
if (!sst->filters) {
|
||||
if (sf.t == VT_POINT) {
|
||||
ai->second.points++;
|
||||
} else if (sf.t == VT_LINE) {
|
||||
ai->second.lines++;
|
||||
} else if (sf.t == VT_POLYGON) {
|
||||
ai->second.polygons++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Internal error: can't find layer name %s\n", sf.layername.c_str());
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
for (ssize_t i = (ssize_t) sf.full_keys.size() - 1; i >= 0; i--) {
|
||||
coerce_value(sf.full_keys[i], sf.full_values[i].type, sf.full_values[i].s, sst->attribute_types);
|
||||
|
||||
if (sf.full_keys[i] == attribute_for_id) {
|
||||
if (sf.full_values[i].type != mvt_double && !additional[A_CONVERT_NUMERIC_IDS]) {
|
||||
static bool warned = false;
|
||||
|
||||
if (!warned) {
|
||||
fprintf(stderr, "Warning: Attribute \"%s\"=\"%s\" as feature ID is not a number\n", sf.full_keys[i].c_str(), sf.full_values[i].s.c_str());
|
||||
warned = true;
|
||||
}
|
||||
} else {
|
||||
char *err;
|
||||
long long id_value = strtoull(sf.full_values[i].s.c_str(), &err, 10);
|
||||
|
||||
if (err != NULL && *err != '\0') {
|
||||
static bool warned_frac = false;
|
||||
|
||||
if (!warned_frac) {
|
||||
fprintf(stderr, "Warning: Can't represent non-integer feature ID %s\n", sf.full_values[i].s.c_str());
|
||||
warned_frac = true;
|
||||
}
|
||||
} else if (std::to_string(id_value) != strip_zeroes(sf.full_values[i].s)) {
|
||||
static bool warned = false;
|
||||
|
||||
if (!warned) {
|
||||
fprintf(stderr, "Warning: Can't represent too-large feature ID %s\n", sf.full_values[i].s.c_str());
|
||||
warned = true;
|
||||
}
|
||||
} else {
|
||||
sf.id = id_value;
|
||||
sf.has_id = true;
|
||||
|
||||
sf.full_keys.erase(sf.full_keys.begin() + i);
|
||||
sf.full_values.erase(sf.full_values.begin() + i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sst->exclude_all) {
|
||||
if (sst->include->count(sf.full_keys[i]) == 0) {
|
||||
sf.full_keys.erase(sf.full_keys.begin() + i);
|
||||
sf.full_values.erase(sf.full_values.begin() + i);
|
||||
continue;
|
||||
}
|
||||
} else if (sst->exclude->count(sf.full_keys[i]) != 0) {
|
||||
sf.full_keys.erase(sf.full_keys.begin() + i);
|
||||
sf.full_values.erase(sf.full_values.begin() + i);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (!sst->filters) {
|
||||
for (size_t i = 0; i < sf.full_keys.size(); i++) {
|
||||
type_and_string attrib;
|
||||
attrib.type = sf.full_values[i].type;
|
||||
attrib.string = sf.full_values[i].s;
|
||||
|
||||
auto fk = sst->layermap->find(sf.layername);
|
||||
add_to_file_keys(fk->second.file_keys, sf.full_keys[i], attrib);
|
||||
}
|
||||
}
|
||||
|
||||
if (inline_meta) {
|
||||
sf.metapos = -1;
|
||||
for (size_t i = 0; i < sf.full_keys.size(); i++) {
|
||||
sf.keys.push_back(addpool(r->poolfile, r->treefile, sf.full_keys[i].c_str(), mvt_string));
|
||||
sf.values.push_back(addpool(r->poolfile, r->treefile, sf.full_values[i].s.c_str(), sf.full_values[i].type));
|
||||
}
|
||||
} else {
|
||||
sf.metapos = r->metapos;
|
||||
serialize_long_long(r->metafile, sf.full_keys.size(), &r->metapos, sst->fname);
|
||||
for (size_t i = 0; i < sf.full_keys.size(); i++) {
|
||||
serialize_long_long(r->metafile, addpool(r->poolfile, r->treefile, sf.full_keys[i].c_str(), mvt_string), &r->metapos, sst->fname);
|
||||
serialize_long_long(r->metafile, addpool(r->poolfile, r->treefile, sf.full_values[i].s.c_str(), sf.full_values[i].type), &r->metapos, sst->fname);
|
||||
}
|
||||
}
|
||||
|
||||
long long geomstart = r->geompos;
|
||||
serialize_feature(r->geomfile, &sf, &r->geompos, sst->fname, SHIFT_RIGHT(*(sst->initial_x)), SHIFT_RIGHT(*(sst->initial_y)), false);
|
||||
|
||||
struct index index;
|
||||
index.start = geomstart;
|
||||
index.end = r->geompos;
|
||||
index.segment = sst->segment;
|
||||
index.seq = *(sst->layer_seq);
|
||||
index.t = sf.t;
|
||||
index.ix = bbox_index;
|
||||
|
||||
fwrite_check(&index, sizeof(struct index), 1, r->indexfile, sst->fname);
|
||||
r->indexpos += sizeof(struct index);
|
||||
|
||||
for (size_t i = 0; i < 2; i++) {
|
||||
if (sf.bbox[i] < r->file_bbox[i]) {
|
||||
r->file_bbox[i] = sf.bbox[i];
|
||||
}
|
||||
}
|
||||
for (size_t i = 2; i < 4; i++) {
|
||||
if (sf.bbox[i] > r->file_bbox[i]) {
|
||||
r->file_bbox[i] = sf.bbox[i];
|
||||
}
|
||||
}
|
||||
|
||||
if (*(sst->progress_seq) % 10000 == 0) {
|
||||
checkdisk(sst->readers);
|
||||
if (!quiet && !quiet_progress && progress_time()) {
|
||||
fprintf(stderr, "Read %.2f million features\r", *sst->progress_seq / 1000000.0);
|
||||
}
|
||||
}
|
||||
(*(sst->progress_seq))++;
|
||||
(*(sst->layer_seq))++;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
void coerce_value(std::string const &key, int &vt, std::string &val, std::map<std::string, int> const *attribute_types) {
|
||||
auto a = (*attribute_types).find(key);
|
||||
if (a != attribute_types->end()) {
|
||||
if (a->second == mvt_string) {
|
||||
vt = mvt_string;
|
||||
} else if (a->second == mvt_float) {
|
||||
vt = mvt_double;
|
||||
val = milo::dtoa_milo(atof(val.c_str()));
|
||||
} else if (a->second == mvt_int) {
|
||||
vt = mvt_double;
|
||||
if (val.size() == 0) {
|
||||
val = "0";
|
||||
}
|
||||
|
||||
for (size_t ii = 0; ii < val.size(); ii++) {
|
||||
char c = val[ii];
|
||||
if (c < '0' || c > '9') {
|
||||
val = std::to_string(round(atof(val.c_str())));
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (a->second == mvt_bool) {
|
||||
if (val == "false" || val == "0" || val == "null" || val.size() == 0 || (vt == mvt_double && atof(val.c_str()) == 0)) {
|
||||
vt = mvt_bool;
|
||||
val = "false";
|
||||
} else {
|
||||
vt = mvt_bool;
|
||||
val = "true";
|
||||
}
|
||||
} else {
|
||||
fprintf(stderr, "Can't happen: attribute type %d\n", a->second);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
196
serial.hpp
196
serial.hpp
@ -1,11 +1,24 @@
|
||||
#ifndef SERIAL_HPP
|
||||
#define SERIAL_HPP
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
#include <atomic>
|
||||
#include <sys/stat.h>
|
||||
#include "geometry.hpp"
|
||||
#include "mbtiles.hpp"
|
||||
#include "jsonpull/jsonpull.h"
|
||||
|
||||
size_t fwrite_check(const void *ptr, size_t size, size_t nitems, FILE *stream, const char *fname);
|
||||
|
||||
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_ulong_long(FILE *out, unsigned 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 serialize_int(FILE *out, int n, std::atomic<long long> *fpos, const char *fname);
|
||||
void serialize_long_long(FILE *out, long long n, std::atomic<long long> *fpos, const char *fname);
|
||||
void serialize_ulong_long(FILE *out, unsigned long long n, std::atomic<long long> *fpos, const char *fname);
|
||||
void serialize_byte(FILE *out, signed char n, std::atomic<long long> *fpos, const char *fname);
|
||||
void serialize_uint(FILE *out, unsigned n, std::atomic<long long> *fpos, const char *fname);
|
||||
void serialize_string(FILE *out, const char *s, std::atomic<long long> *fpos, const char *fname);
|
||||
|
||||
void deserialize_int(char **f, int *n);
|
||||
void deserialize_long_long(char **f, long long *n);
|
||||
@ -13,37 +26,146 @@ void deserialize_ulong_long(char **f, unsigned long long *n);
|
||||
void deserialize_uint(char **f, unsigned *n);
|
||||
void deserialize_byte(char **f, signed char *n);
|
||||
|
||||
int deserialize_int_io(FILE *f, int *n, long long *geompos);
|
||||
int deserialize_long_long_io(FILE *f, long long *n, long long *geompos);
|
||||
int deserialize_ulong_long_io(FILE *f, unsigned long long *n, long long *geompos);
|
||||
int deserialize_uint_io(FILE *f, unsigned *n, long long *geompos);
|
||||
int deserialize_byte_io(FILE *f, signed char *n, long long *geompos);
|
||||
int deserialize_int_io(FILE *f, int *n, std::atomic<long long> *geompos);
|
||||
int deserialize_long_long_io(FILE *f, long long *n, std::atomic<long long> *geompos);
|
||||
int deserialize_ulong_long_io(FILE *f, unsigned long long *n, std::atomic<long long> *geompos);
|
||||
int deserialize_uint_io(FILE *f, unsigned *n, std::atomic<long long> *geompos);
|
||||
int deserialize_byte_io(FILE *f, signed char *n, std::atomic<long long> *geompos);
|
||||
|
||||
struct serial_feature {
|
||||
long long layer;
|
||||
int segment;
|
||||
long long seq;
|
||||
|
||||
signed char t;
|
||||
signed char feature_minzoom;
|
||||
|
||||
bool has_id;
|
||||
unsigned long long id;
|
||||
|
||||
bool has_tippecanoe_minzoom;
|
||||
int tippecanoe_minzoom;
|
||||
|
||||
bool has_tippecanoe_maxzoom;
|
||||
int tippecanoe_maxzoom;
|
||||
|
||||
drawvec geometry;
|
||||
unsigned long long index;
|
||||
long long extent;
|
||||
|
||||
size_t m;
|
||||
std::vector<long long> keys;
|
||||
std::vector<long long> values;
|
||||
long long metapos;
|
||||
struct serial_val {
|
||||
int type = 0;
|
||||
std::string s = "";
|
||||
};
|
||||
|
||||
void serialize_feature(FILE *geomfile, serial_feature *sf, long long *geompos, const char *fname, long long wx, long long wy, bool include_minzoom);
|
||||
struct serial_feature {
|
||||
long long layer = 0;
|
||||
int segment = 0;
|
||||
long long seq = 0;
|
||||
|
||||
signed char t = 0;
|
||||
signed char feature_minzoom = 0;
|
||||
|
||||
bool has_id = false;
|
||||
unsigned long long id = 0;
|
||||
|
||||
bool has_tippecanoe_minzoom = false;
|
||||
int tippecanoe_minzoom = 0;
|
||||
|
||||
bool has_tippecanoe_maxzoom = false;
|
||||
int tippecanoe_maxzoom = 0;
|
||||
|
||||
drawvec geometry = drawvec();
|
||||
unsigned long long index = 0;
|
||||
long long extent = 0;
|
||||
|
||||
std::vector<long long> keys{};
|
||||
std::vector<long long> values{};
|
||||
// If >= 0, metadata is external
|
||||
long long metapos = 0;
|
||||
|
||||
// XXX This isn't serialized. Should it be here?
|
||||
long long bbox[4] = {0, 0, 0, 0};
|
||||
std::vector<std::string> full_keys{};
|
||||
std::vector<serial_val> full_values{};
|
||||
std::string layername = "";
|
||||
bool dropped = false;
|
||||
};
|
||||
|
||||
void serialize_feature(FILE *geomfile, serial_feature *sf, std::atomic<long long> *geompos, const char *fname, long long wx, long long wy, bool include_minzoom);
|
||||
serial_feature deserialize_feature(FILE *geoms, std::atomic<long long> *geompos_in, char *metabase, long long *meta_off, unsigned z, unsigned tx, unsigned ty, unsigned *initial_x, unsigned *initial_y);
|
||||
|
||||
struct reader {
|
||||
int metafd = -1;
|
||||
int poolfd = -1;
|
||||
int treefd = -1;
|
||||
int geomfd = -1;
|
||||
int indexfd = -1;
|
||||
|
||||
FILE *metafile = NULL;
|
||||
struct memfile *poolfile = NULL;
|
||||
struct memfile *treefile = NULL;
|
||||
FILE *geomfile = NULL;
|
||||
FILE *indexfile = NULL;
|
||||
|
||||
std::atomic<long long> metapos;
|
||||
std::atomic<long long> geompos;
|
||||
std::atomic<long long> indexpos;
|
||||
|
||||
long long file_bbox[4] = {0, 0, 0, 0};
|
||||
|
||||
struct stat geomst {};
|
||||
struct stat metast {};
|
||||
|
||||
char *geom_map = NULL;
|
||||
|
||||
reader()
|
||||
: metapos(0), geompos(0), indexpos(0) {
|
||||
}
|
||||
|
||||
reader(reader const &r) {
|
||||
metafd = r.metafd;
|
||||
poolfd = r.poolfd;
|
||||
treefd = r.treefd;
|
||||
geomfd = r.geomfd;
|
||||
indexfd = r.indexfd;
|
||||
|
||||
metafile = r.metafile;
|
||||
poolfile = r.poolfile;
|
||||
treefile = r.treefile;
|
||||
geomfile = r.geomfile;
|
||||
indexfile = r.indexfile;
|
||||
|
||||
long long p = r.metapos;
|
||||
metapos = p;
|
||||
|
||||
p = r.geompos;
|
||||
geompos = p;
|
||||
|
||||
p = r.indexpos;
|
||||
indexpos = p;
|
||||
|
||||
memcpy(file_bbox, r.file_bbox, sizeof(file_bbox));
|
||||
|
||||
geomst = r.geomst;
|
||||
metast = r.metast;
|
||||
|
||||
geom_map = r.geom_map;
|
||||
}
|
||||
};
|
||||
|
||||
struct serialization_state {
|
||||
const char *fname = NULL; // source file name
|
||||
int line = 0; // user-oriented location within source for error reports
|
||||
|
||||
std::atomic<long long> *layer_seq = NULL; // sequence within current layer
|
||||
std::atomic<long long> *progress_seq = NULL; // overall sequence for progress indicator
|
||||
|
||||
std::vector<struct reader> *readers = NULL; // array of data for each input thread
|
||||
int segment = 0; // the current input thread
|
||||
|
||||
unsigned *initial_x = NULL; // relative offset of all geometries
|
||||
unsigned *initial_y = NULL;
|
||||
int *initialized = NULL;
|
||||
|
||||
double *dist_sum = NULL; // running tally for calculation of resolution within features
|
||||
size_t *dist_count = NULL;
|
||||
bool want_dist = false;
|
||||
|
||||
int maxzoom = 0;
|
||||
int basezoom = 0;
|
||||
|
||||
bool filters = false;
|
||||
bool uses_gamma = false;
|
||||
|
||||
std::map<std::string, layermap_entry> *layermap = NULL;
|
||||
|
||||
std::map<std::string, int> const *attribute_types = NULL;
|
||||
std::set<std::string> *exclude = NULL;
|
||||
std::set<std::string> *include = NULL;
|
||||
int exclude_all = 0;
|
||||
};
|
||||
|
||||
int serialize_feature(struct serialization_state *sst, serial_feature &sf);
|
||||
void coerce_value(std::string const &key, int &vt, std::string &val, std::map<std::string, int> const *attribute_types);
|
||||
|
||||
#endif
|
||||
|
100
tests/accumulate/in.json
Normal file
100
tests/accumulate/in.json
Normal file
@ -0,0 +1,100 @@
|
||||
{ "type": "Feature", "properties": { "thesum": 1, "themax": 1, "themin": 1, "theproduct": 1, "themean": 1, "theconcat": 1, "thecomma": 1 }, "geometry": { "type": "Point", "coordinates": [ 134.198884,-54.297477 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 2, "themax": 2, "themin": 2, "theproduct": 2, "themean": 2, "theconcat": 2, "thecomma": 2 }, "geometry": { "type": "Point", "coordinates": [ 14.059736,46.728490 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 3, "themax": 3, "themin": 3, "theproduct": 3, "themean": 3, "theconcat": 3, "thecomma": 3 }, "geometry": { "type": "Point", "coordinates": [ -147.369727,84.781281 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 4, "themax": 4, "themin": 4, "theproduct": 4, "themean": 4, "theconcat": 4, "thecomma": 4 }, "geometry": { "type": "Point", "coordinates": [ -66.234387,89.199903 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 5, "themax": 5, "themin": 5, "theproduct": 5, "themean": 5, "theconcat": 5, "thecomma": 5 }, "geometry": { "type": "Point", "coordinates": [ -153.412664,29.072260 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 6, "themax": 6, "themin": 6, "theproduct": 6, "themean": 6, "theconcat": 6, "thecomma": 6 }, "geometry": { "type": "Point", "coordinates": [ 170.262110,-59.450058 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 7, "themax": 7, "themin": 7, "theproduct": 7, "themean": 7, "theconcat": 7, "thecomma": 7 }, "geometry": { "type": "Point", "coordinates": [ 126.605140,8.723849 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 8, "themax": 8, "themin": 8, "theproduct": 8, "themean": 8, "theconcat": 8, "thecomma": 8 }, "geometry": { "type": "Point", "coordinates": [ 161.440413,-40.375296 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 9, "themax": 9, "themin": 9, "theproduct": 9, "themean": 9, "theconcat": 9, "thecomma": 9 }, "geometry": { "type": "Point", "coordinates": [ -168.588146,-52.640848 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 10, "themax": 10, "themin": 10, "theproduct": 10, "themean": 10, "theconcat": 10, "thecomma": 10 }, "geometry": { "type": "Point", "coordinates": [ 87.732615,-76.468219 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 11, "themax": 11, "themin": 11, "theproduct": 11, "themean": 11, "theconcat": 11, "thecomma": 11 }, "geometry": { "type": "Point", "coordinates": [ 30.857236,80.182977 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 12, "themax": 12, "themin": 12, "theproduct": 12, "themean": 12, "theconcat": 12, "thecomma": 12 }, "geometry": { "type": "Point", "coordinates": [ -145.428454,26.334493 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 13, "themax": 13, "themin": 13, "theproduct": 13, "themean": 13, "theconcat": 13, "thecomma": 13 }, "geometry": { "type": "Point", "coordinates": [ -3.709986,47.351847 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 14, "themax": 14, "themin": 14, "theproduct": 14, "themean": 14, "theconcat": 14, "thecomma": 14 }, "geometry": { "type": "Point", "coordinates": [ -145.079976,80.547441 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 15, "themax": 15, "themin": 15, "theproduct": 15, "themean": 15, "theconcat": 15, "thecomma": 15 }, "geometry": { "type": "Point", "coordinates": [ 14.666648,-65.254575 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 16, "themax": 16, "themin": 16, "theproduct": 16, "themean": 16, "theconcat": 16, "thecomma": 16 }, "geometry": { "type": "Point", "coordinates": [ 24.659014,60.295014 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 17, "themax": 17, "themin": 17, "theproduct": 17, "themean": 17, "theconcat": 17, "thecomma": 17 }, "geometry": { "type": "Point", "coordinates": [ 110.427380,38.364152 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 18, "themax": 18, "themin": 18, "theproduct": 18, "themean": 18, "theconcat": 18, "thecomma": 18 }, "geometry": { "type": "Point", "coordinates": [ 6.564431,-44.802836 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 19, "themax": 19, "themin": 19, "theproduct": 19, "themean": 19, "theconcat": 19, "thecomma": 19 }, "geometry": { "type": "Point", "coordinates": [ -25.148684,-41.191936 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 20, "themax": 20, "themin": 20, "theproduct": 20, "themean": 20, "theconcat": 20, "thecomma": 20 }, "geometry": { "type": "Point", "coordinates": [ 94.640674,87.473404 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 21, "themax": 21, "themin": 21, "theproduct": 21, "themean": 21, "theconcat": 21, "thecomma": 21 }, "geometry": { "type": "Point", "coordinates": [ -15.166749,44.154022 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 22, "themax": 22, "themin": 22, "theproduct": 22, "themean": 22, "theconcat": 22, "thecomma": 22 }, "geometry": { "type": "Point", "coordinates": [ -83.839224,-22.324076 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 23, "themax": 23, "themin": 23, "theproduct": 23, "themean": 23, "theconcat": 23, "thecomma": 23 }, "geometry": { "type": "Point", "coordinates": [ -131.431059,-48.656317 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 24, "themax": 24, "themin": 24, "theproduct": 24, "themean": 24, "theconcat": 24, "thecomma": 24 }, "geometry": { "type": "Point", "coordinates": [ 13.458676,-81.541789 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 25, "themax": 25, "themin": 25, "theproduct": 25, "themean": 25, "theconcat": 25, "thecomma": 25 }, "geometry": { "type": "Point", "coordinates": [ 49.375287,54.035848 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 26, "themax": 26, "themin": 26, "theproduct": 26, "themean": 26, "theconcat": 26, "thecomma": 26 }, "geometry": { "type": "Point", "coordinates": [ 44.023761,42.750924 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 27, "themax": 27, "themin": 27, "theproduct": 27, "themean": 27, "theconcat": 27, "thecomma": 27 }, "geometry": { "type": "Point", "coordinates": [ 23.815303,87.689971 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 28, "themax": 28, "themin": 28, "theproduct": 28, "themean": 28, "theconcat": 28, "thecomma": 28 }, "geometry": { "type": "Point", "coordinates": [ -108.418101,69.859222 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 29, "themax": 29, "themin": 29, "theproduct": 29, "themean": 29, "theconcat": 29, "thecomma": 29 }, "geometry": { "type": "Point", "coordinates": [ -162.422461,-72.828993 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 30, "themax": 30, "themin": 30, "theproduct": 30, "themean": 30, "theconcat": 30, "thecomma": 30 }, "geometry": { "type": "Point", "coordinates": [ 41.555960,87.834643 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 31, "themax": 31, "themin": 31, "theproduct": 31, "themean": 31, "theconcat": 31, "thecomma": 31 }, "geometry": { "type": "Point", "coordinates": [ 151.433696,-2.049808 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 32, "themax": 32, "themin": 32, "theproduct": 32, "themean": 32, "theconcat": 32, "thecomma": 32 }, "geometry": { "type": "Point", "coordinates": [ -156.347397,-54.157799 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 33, "themax": 33, "themin": 33, "theproduct": 33, "themean": 33, "theconcat": 33, "thecomma": 33 }, "geometry": { "type": "Point", "coordinates": [ 177.449262,76.384948 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 34, "themax": 34, "themin": 34, "theproduct": 34, "themean": 34, "theconcat": 34, "thecomma": 34 }, "geometry": { "type": "Point", "coordinates": [ 67.516237,89.283900 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 35, "themax": 35, "themin": 35, "theproduct": 35, "themean": 35, "theconcat": 35, "thecomma": 35 }, "geometry": { "type": "Point", "coordinates": [ 57.957991,-82.230182 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 36, "themax": 36, "themin": 36, "theproduct": 36, "themean": 36, "theconcat": 36, "thecomma": 36 }, "geometry": { "type": "Point", "coordinates": [ 152.745823,-78.389341 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 37, "themax": 37, "themin": 37, "theproduct": 37, "themean": 37, "theconcat": 37, "thecomma": 37 }, "geometry": { "type": "Point", "coordinates": [ -79.735349,80.847381 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 38, "themax": 38, "themin": 38, "theproduct": 38, "themean": 38, "theconcat": 38, "thecomma": 38 }, "geometry": { "type": "Point", "coordinates": [ -176.469083,-1.175014 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 39, "themax": 39, "themin": 39, "theproduct": 39, "themean": 39, "theconcat": 39, "thecomma": 39 }, "geometry": { "type": "Point", "coordinates": [ 20.369644,-44.945809 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 40, "themax": 40, "themin": 40, "theproduct": 40, "themean": 40, "theconcat": 40, "thecomma": 40 }, "geometry": { "type": "Point", "coordinates": [ 75.300338,89.728838 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 41, "themax": 41, "themin": 41, "theproduct": 41, "themean": 41, "theconcat": 41, "thecomma": 41 }, "geometry": { "type": "Point", "coordinates": [ -60.903503,-89.434640 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 42, "themax": 42, "themin": 42, "theproduct": 42, "themean": 42, "theconcat": 42, "thecomma": 42 }, "geometry": { "type": "Point", "coordinates": [ -116.214566,-78.600594 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 43, "themax": 43, "themin": 43, "theproduct": 43, "themean": 43, "theconcat": 43, "thecomma": 43 }, "geometry": { "type": "Point", "coordinates": [ -168.143210,-37.381940 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 44, "themax": 44, "themin": 44, "theproduct": 44, "themean": 44, "theconcat": 44, "thecomma": 44 }, "geometry": { "type": "Point", "coordinates": [ 146.600367,-65.112907 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 45, "themax": 45, "themin": 45, "theproduct": 45, "themean": 45, "theconcat": 45, "thecomma": 45 }, "geometry": { "type": "Point", "coordinates": [ -144.960448,89.877412 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 46, "themax": 46, "themin": 46, "theproduct": 46, "themean": 46, "theconcat": 46, "thecomma": 46 }, "geometry": { "type": "Point", "coordinates": [ 37.779538,-9.255682 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 47, "themax": 47, "themin": 47, "theproduct": 47, "themean": 47, "theconcat": 47, "thecomma": 47 }, "geometry": { "type": "Point", "coordinates": [ 141.921366,-86.191382 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 48, "themax": 48, "themin": 48, "theproduct": 48, "themean": 48, "theconcat": 48, "thecomma": 48 }, "geometry": { "type": "Point", "coordinates": [ 3.757458,-46.957098 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 49, "themax": 49, "themin": 49, "theproduct": 49, "themean": 49, "theconcat": 49, "thecomma": 49 }, "geometry": { "type": "Point", "coordinates": [ -178.557808,-17.999930 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 50, "themax": 50, "themin": 50, "theproduct": 50, "themean": 50, "theconcat": 50, "thecomma": 50 }, "geometry": { "type": "Point", "coordinates": [ -94.775598,89.666291 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 51, "themax": 51, "themin": 51, "theproduct": 51, "themean": 51, "theconcat": 51, "thecomma": 51 }, "geometry": { "type": "Point", "coordinates": [ -105.311977,25.819445 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 52, "themax": 52, "themin": 52, "theproduct": 52, "themean": 52, "theconcat": 52, "thecomma": 52 }, "geometry": { "type": "Point", "coordinates": [ 116.462344,-73.172234 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 53, "themax": 53, "themin": 53, "theproduct": 53, "themean": 53, "theconcat": 53, "thecomma": 53 }, "geometry": { "type": "Point", "coordinates": [ -84.902185,62.166503 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 54, "themax": 54, "themin": 54, "theproduct": 54, "themean": 54, "theconcat": 54, "thecomma": 54 }, "geometry": { "type": "Point", "coordinates": [ -141.394664,-11.763473 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 55, "themax": 55, "themin": 55, "theproduct": 55, "themean": 55, "theconcat": 55, "thecomma": 55 }, "geometry": { "type": "Point", "coordinates": [ 96.026399,83.713544 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 56, "themax": 56, "themin": 56, "theproduct": 56, "themean": 56, "theconcat": 56, "thecomma": 56 }, "geometry": { "type": "Point", "coordinates": [ -154.653153,-89.307708 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 57, "themax": 57, "themin": 57, "theproduct": 57, "themean": 57, "theconcat": 57, "thecomma": 57 }, "geometry": { "type": "Point", "coordinates": [ -71.769818,83.010189 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 58, "themax": 58, "themin": 58, "theproduct": 58, "themean": 58, "theconcat": 58, "thecomma": 58 }, "geometry": { "type": "Point", "coordinates": [ 141.629089,-84.969764 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 59, "themax": 59, "themin": 59, "theproduct": 59, "themean": 59, "theconcat": 59, "thecomma": 59 }, "geometry": { "type": "Point", "coordinates": [ -146.893857,-72.900555 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 60, "themax": 60, "themin": 60, "theproduct": 60, "themean": 60, "theconcat": 60, "thecomma": 60 }, "geometry": { "type": "Point", "coordinates": [ 13.616243,86.468765 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 61, "themax": 61, "themin": 61, "theproduct": 61, "themean": 61, "theconcat": 61, "thecomma": 61 }, "geometry": { "type": "Point", "coordinates": [ 146.239761,-77.415130 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 62, "themax": 62, "themin": 62, "theproduct": 62, "themean": 62, "theconcat": 62, "thecomma": 62 }, "geometry": { "type": "Point", "coordinates": [ 120.720040,34.052368 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 63, "themax": 63, "themin": 63, "theproduct": 63, "themean": 63, "theconcat": 63, "thecomma": 63 }, "geometry": { "type": "Point", "coordinates": [ -50.738121,-49.044879 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 64, "themax": 64, "themin": 64, "theproduct": 64, "themean": 64, "theconcat": 64, "thecomma": 64 }, "geometry": { "type": "Point", "coordinates": [ 159.682795,-17.137421 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 65, "themax": 65, "themin": 65, "theproduct": 65, "themean": 65, "theconcat": 65, "thecomma": 65 }, "geometry": { "type": "Point", "coordinates": [ -166.439086,2.775299 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 66, "themax": 66, "themin": 66, "theproduct": 66, "themean": 66, "theconcat": 66, "thecomma": 66 }, "geometry": { "type": "Point", "coordinates": [ -68.075084,-6.863559 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 67, "themax": 67, "themin": 67, "theproduct": 67, "themean": 67, "theconcat": 67, "thecomma": 67 }, "geometry": { "type": "Point", "coordinates": [ -177.446873,22.270257 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 68, "themax": 68, "themin": 68, "theproduct": 68, "themean": 68, "theconcat": 68, "thecomma": 68 }, "geometry": { "type": "Point", "coordinates": [ 116.851469,-89.159093 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 69, "themax": 69, "themin": 69, "theproduct": 69, "themean": 69, "theconcat": 69, "thecomma": 69 }, "geometry": { "type": "Point", "coordinates": [ 113.612595,13.841587 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 70, "themax": 70, "themin": 70, "theproduct": 70, "themean": 70, "theconcat": 70, "thecomma": 70 }, "geometry": { "type": "Point", "coordinates": [ 147.030951,-21.627240 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 71, "themax": 71, "themin": 71, "theproduct": 71, "themean": 71, "theconcat": 71, "thecomma": 71 }, "geometry": { "type": "Point", "coordinates": [ -171.838003,-55.021829 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 72, "themax": 72, "themin": 72, "theproduct": 72, "themean": 72, "theconcat": 72, "thecomma": 72 }, "geometry": { "type": "Point", "coordinates": [ -179.320808,-69.030987 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 73, "themax": 73, "themin": 73, "theproduct": 73, "themean": 73, "theconcat": 73, "thecomma": 73 }, "geometry": { "type": "Point", "coordinates": [ -41.291951,-0.546778 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 74, "themax": 74, "themin": 74, "theproduct": 74, "themean": 74, "theconcat": 74, "thecomma": 74 }, "geometry": { "type": "Point", "coordinates": [ -159.602264,33.516804 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 75, "themax": 75, "themin": 75, "theproduct": 75, "themean": 75, "theconcat": 75, "thecomma": 75 }, "geometry": { "type": "Point", "coordinates": [ -157.220701,-62.925493 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 76, "themax": 76, "themin": 76, "theproduct": 76, "themean": 76, "theconcat": 76, "thecomma": 76 }, "geometry": { "type": "Point", "coordinates": [ 35.449285,-21.548332 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 77, "themax": 77, "themin": 77, "theproduct": 77, "themean": 77, "theconcat": 77, "thecomma": 77 }, "geometry": { "type": "Point", "coordinates": [ 101.921779,85.807116 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 78, "themax": 78, "themin": 78, "theproduct": 78, "themean": 78, "theconcat": 78, "thecomma": 78 }, "geometry": { "type": "Point", "coordinates": [ -128.051674,83.348143 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 79, "themax": 79, "themin": 79, "theproduct": 79, "themean": 79, "theconcat": 79, "thecomma": 79 }, "geometry": { "type": "Point", "coordinates": [ 53.387503,-84.129458 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 80, "themax": 80, "themin": 80, "theproduct": 80, "themean": 80, "theconcat": 80, "thecomma": 80 }, "geometry": { "type": "Point", "coordinates": [ 58.273040,-53.030862 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 81, "themax": 81, "themin": 81, "theproduct": 81, "themean": 81, "theconcat": 81, "thecomma": 81 }, "geometry": { "type": "Point", "coordinates": [ 125.294919,18.287339 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 82, "themax": 82, "themin": 82, "theproduct": 82, "themean": 82, "theconcat": 82, "thecomma": 82 }, "geometry": { "type": "Point", "coordinates": [ 31.960448,55.594735 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 83, "themax": 83, "themin": 83, "theproduct": 83, "themean": 83, "theconcat": 83, "thecomma": 83 }, "geometry": { "type": "Point", "coordinates": [ 43.356826,-79.995205 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 84, "themax": 84, "themin": 84, "theproduct": 84, "themean": 84, "theconcat": 84, "thecomma": 84 }, "geometry": { "type": "Point", "coordinates": [ -117.349281,13.354257 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 85, "themax": 85, "themin": 85, "theproduct": 85, "themean": 85, "theconcat": 85, "thecomma": 85 }, "geometry": { "type": "Point", "coordinates": [ 7.392891,-80.387558 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 86, "themax": 86, "themin": 86, "theproduct": 86, "themean": 86, "theconcat": 86, "thecomma": 86 }, "geometry": { "type": "Point", "coordinates": [ -0.255971,-32.692982 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 87, "themax": 87, "themin": 87, "theproduct": 87, "themean": 87, "theconcat": 87, "thecomma": 87 }, "geometry": { "type": "Point", "coordinates": [ 165.528504,-69.862641 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 88, "themax": 88, "themin": 88, "theproduct": 88, "themean": 88, "theconcat": 88, "thecomma": 88 }, "geometry": { "type": "Point", "coordinates": [ -137.105444,-88.260877 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 89, "themax": 89, "themin": 89, "theproduct": 89, "themean": 89, "theconcat": 89, "thecomma": 89 }, "geometry": { "type": "Point", "coordinates": [ 28.112105,24.529585 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 90, "themax": 90, "themin": 90, "theproduct": 90, "themean": 90, "theconcat": 90, "thecomma": 90 }, "geometry": { "type": "Point", "coordinates": [ -29.013503,48.460714 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 91, "themax": 91, "themin": 91, "theproduct": 91, "themean": 91, "theconcat": 91, "thecomma": 91 }, "geometry": { "type": "Point", "coordinates": [ 114.967139,84.177181 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 92, "themax": 92, "themin": 92, "theproduct": 92, "themean": 92, "theconcat": 92, "thecomma": 92 }, "geometry": { "type": "Point", "coordinates": [ -63.363328,58.006985 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 93, "themax": 93, "themin": 93, "theproduct": 93, "themean": 93, "theconcat": 93, "thecomma": 93 }, "geometry": { "type": "Point", "coordinates": [ 169.765226,17.852955 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 94, "themax": 94, "themin": 94, "theproduct": 94, "themean": 94, "theconcat": 94, "thecomma": 94 }, "geometry": { "type": "Point", "coordinates": [ -132.507302,18.086814 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 95, "themax": 95, "themin": 95, "theproduct": 95, "themean": 95, "theconcat": 95, "thecomma": 95 }, "geometry": { "type": "Point", "coordinates": [ -56.678179,-62.556708 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 96, "themax": 96, "themin": 96, "theproduct": 96, "themean": 96, "theconcat": 96, "thecomma": 96 }, "geometry": { "type": "Point", "coordinates": [ 174.715105,-76.783064 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 97, "themax": 97, "themin": 97, "theproduct": 97, "themean": 97, "theconcat": 97, "thecomma": 97 }, "geometry": { "type": "Point", "coordinates": [ -19.142632,85.007238 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 98, "themax": 98, "themin": 98, "theproduct": 98, "themean": 98, "theconcat": 98, "thecomma": 98 }, "geometry": { "type": "Point", "coordinates": [ 58.241524,86.371735 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 99, "themax": 99, "themin": 99, "theproduct": 99, "themean": 99, "theconcat": 99, "thecomma": 99 }, "geometry": { "type": "Point", "coordinates": [ -27.756470,53.336764 ] } }
|
||||
{ "type": "Feature", "properties": { "thesum": 100, "themax": 100, "themin": 100, "theproduct": 100, "themean": 100, "theconcat": 100, "thecomma": 100 }, "geometry": { "type": "Point", "coordinates": [ -123.948513,-81.873970 ] } }
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
3632
tests/allow-existing/both.mbtiles.json
Normal file
3632
tests/allow-existing/both.mbtiles.json
Normal file
File diff suppressed because one or more lines are too long
39
tests/attribute-type/in.json
Normal file
39
tests/attribute-type/in.json
Normal file
@ -0,0 +1,39 @@
|
||||
{ "type": "Feature", "properties": { "booltype": null, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "booltype": "null", "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "booltype": "", "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "booltype": 0, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "booltype": "0", "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "booltype": "false", "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "booltype": true, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "booltype": 1, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "booltype": 0.0, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "booltype": "yes", "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "booltype": [ 2, 3 ], "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "stringtype": null, "expect": "null" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "stringtype": 2, "expect": "2" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "stringtype": 2.5, "expect": "2.5" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "stringtype": true, "expect": "true" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "stringtype": false, "expect": "false" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "stringtype": "something", "expect": "something" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "stringtype": [ 2, 3 ], "expect": "[2,3]" }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "inttype": null, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "inttype": "5", "expect": 5 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "inttype": "5.6", "expect": 6 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "inttype": 5.6, "expect": 6 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "inttype": true, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "inttype": false, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "inttype": "", "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "inttype": "yes", "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "inttype": " 3", "expect": 3 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "inttype": [ 2, 3 ], "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "floattype": null, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "floattype": "5", "expect": 5 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "floattype": "5.6", "expect": 5.6 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "floattype": 5.6, "expect": 5.6 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "floattype": true, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "floattype": false, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "floattype": "", "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "floattype": "yes", "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "floattype": " 3", "expect": 3 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
||||
{ "type": "Feature", "properties": { "floattype": [ 2, 3 ], "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0,0 ] } }
|
@ -0,0 +1,95 @@
|
||||
{ "type": "FeatureCollection", "properties": {
|
||||
"bounds": "0.000000,0.000000,0.000000,0.000000",
|
||||
"center": "0.000000,0.000000,0",
|
||||
"description": "tests/attribute-type/out/-z0_-Tinttype@int_-Tfloattype@float_-Tbooltype@bool_-Tstringtype@string.json.check.mbtiles",
|
||||
"format": "pbf",
|
||||
"generator_options": "./tippecanoe -q -a@ -f -o tests/attribute-type/out/-z0_-Tinttype@int_-Tfloattype@float_-Tbooltype@bool_-Tstringtype@string.json.check.mbtiles -z0 -Tinttype:int -Tfloattype:float -Tbooltype:bool -Tstringtype:string tests/attribute-type/in.json",
|
||||
"json": "{\"vector_layers\": [ { \"id\": \"in\", \"description\": \"\", \"minzoom\": 0, \"maxzoom\": 0, \"fields\": {\"booltype\": \"Boolean\", \"expect\": \"Mixed\", \"floattype\": \"Number\", \"inttype\": \"Number\", \"stringtype\": \"String\"} } ],\"tilestats\": {\"layerCount\": 1,\"layers\": [{\"layer\": \"in\",\"count\": 39,\"geometry\": \"Point\",\"attributeCount\": 5,\"attributes\": [{\"attribute\": \"booltype\",\"count\": 2,\"type\": \"boolean\",\"values\": [false,true]},{\"attribute\": \"expect\",\"count\": 14,\"type\": \"mixed\",\"values\": [0,\"2\",\"2.5\",3,5,5.6,6,\"[2,3]\",\"false\",false,\"null\",\"something\",\"true\",true],\"min\": 0,\"max\": 6},{\"attribute\": \"floattype\",\"count\": 4,\"type\": \"number\",\"values\": [0,3,5,5.6],\"min\": 0,\"max\": 5.6},{\"attribute\": \"inttype\",\"count\": 5,\"type\": \"number\",\"values\": [0,0.000000,3.000000,5,6.000000],\"min\": 0,\"max\": 6},{\"attribute\": \"stringtype\",\"count\": 7,\"type\": \"string\",\"values\": [\"2\",\"2.5\",\"[2,3]\",\"false\",\"null\",\"something\",\"true\"]}]}]}}",
|
||||
"maxzoom": "0",
|
||||
"minzoom": "0",
|
||||
"name": "tests/attribute-type/out/-z0_-Tinttype@int_-Tfloattype@float_-Tbooltype@bool_-Tstringtype@string.json.check.mbtiles",
|
||||
"type": "overlay",
|
||||
"version": "2"
|
||||
}, "features": [
|
||||
{ "type": "FeatureCollection", "properties": { "zoom": 0, "x": 0, "y": 0 }, "features": [
|
||||
{ "type": "FeatureCollection", "properties": { "layer": "in", "version": 2, "extent": 4096 }, "features": [
|
||||
{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "booltype": true, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "booltype": true, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "booltype": false, "expect": false }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "booltype": true, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "booltype": true, "expect": true }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "stringtype": "null", "expect": "null" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "stringtype": "2", "expect": "2" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "stringtype": "2.5", "expect": "2.5" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "stringtype": "true", "expect": "true" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "stringtype": "false", "expect": "false" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "stringtype": "something", "expect": "something" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "stringtype": "[2,3]", "expect": "[2,3]" }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "inttype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "inttype": 5, "expect": 5 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "inttype": 6, "expect": 6 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "inttype": 6, "expect": 6 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "inttype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "inttype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "inttype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "inttype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "inttype": 3, "expect": 3 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "inttype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "floattype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "floattype": 5, "expect": 5 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "floattype": 5.6, "expect": 5.6 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "floattype": 5.6, "expect": 5.6 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "floattype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "floattype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "floattype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "floattype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "floattype": 3, "expect": 3 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
,
|
||||
{ "type": "Feature", "properties": { "floattype": 0, "expect": 0 }, "geometry": { "type": "Point", "coordinates": [ 0.000000, 0.000000 ] } }
|
||||
] }
|
||||
] }
|
||||
] }
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user