fix handling of buildings with inner holes by breaking up polygons into their rings
[osmrrze.git] / scripts / osmbuildings-json-generator.pl
CommitLineData
d58b8e00
MPM
1#!/usr/bin/perl -w
2
3# Our database
4$dbname = 'osm';
5
6# Default verbosity level? Can be increased with -v, decreased with -q
7$verblev = 0;
8
9# The area which we cover
10$x1 = 11.01;
11$y1 = 49.56;
12$x2 = 11.04;
13$y2 = 49.58;
14
15# There should be no need to touch anything below this line.
16# ---------------------------------------------------------------------------
17
18use DBI;
19use POSIX qw(strftime mktime);
20use Pg::hstore;
6b3575c2 21use JSON;
d58b8e00
MPM
22
23# Par. 0: Level on which this gets printed
24# Par. 1: Text
25sub printlev($$) {
26 unless (defined($printlev_atbeginofline)) { $printlev_atbeginofline = 1; }
27 if ($verblev >= $_[0]) {
28 if ($printlev_atbeginofline) {
29 print(strftime("[%Y%m%d-%H%M%S] ", localtime(time())));
30 }
31 print($_[1]);
32 if ($_[1] =~ m/\n$/) {
33 $printlev_atbeginofline = 1;
34 } else {
35 $printlev_atbeginofline = 0;
36 }
37 }
38}
39
40# Par. 0: default value to return if nothing is defined
41# Par. 1: the hashref containing our variables.
42# Par. 2: variables to query. The LAST one that exists will be returned.
43sub fetchlastofhr($$@) {
44 my $res; my $hr; my @vns;
45 ($res, $hr, @vns) = @_;
46 for (my $i = 0; $i < @vns; $i++) {
47 my $vn = $vns[$i];
48 if (defined($hr->{$vn})) {
49 $res = $hr->{$vn};
50 }
51 }
52 return $res;
53}
54
e044730a
MPM
55# Helper functions for the CGI variant. Taken from the openstreetmap-Wiki,
56# which in turn is almost a direct copy of a GPL'd script included in (amongst
57# others) the mapnik sourcecode. -> GPL code
9de6d037 58use Math::Trig;
e044730a 59sub Project($$$) {
9de6d037
MPM
60 my ($X, $Y, $Zoom) = @_;
61 my $Unit = 1 / (2 ** $Zoom);
62 my $relY1 = $Y * $Unit;
63 my $relY2 = $relY1 + $Unit;
9de6d037
MPM
64 my $LimitY = pi;
65 my $RangeY = 2 * $LimitY;
66 $relY1 = $LimitY - $RangeY * $relY1;
67 $relY2 = $LimitY - $RangeY * $relY2;
68 my $Lat1 = ProjectMercToLat($relY1);
69 my $Lat2 = ProjectMercToLat($relY2);
70 $Unit = 360 / (2 ** $Zoom);
71 my $Long1 = -180 + $X * $Unit;
72 return ($Lat2, $Long1, $Lat1, $Long1 + $Unit); # S,W,N,E
73}
e044730a
MPM
74sub ProjectMercToLat($) {
75 return rad2deg(atan(sinh($_[0])));
9de6d037 76}
e044730a
MPM
77sub ProjectF($) {
78 $Lat = deg2rad($_[0]);
79 return (log(tan($Lat) + sec($Lat)));
9de6d037 80}
e044730a 81# ----- end of copied helper functions -----
9de6d037 82
e79ebcd6
MPM
83# print that drops whitespace when not in verbose mode.
84sub printwows($) {
85 my $res = $_[0];
86 if ($verblev > 0) {
87 print($res); return;
88 }
89 $res =~ s/^\s+//g;
90 $res =~ s/"\s*:\s*/":/g;
91 $res =~ s/[\r\n]//g;
92 print($res);
93}
94
d58b8e00
MPM
95# ----------------------------------------------------------------------------
96# main()
97# ----------------------------------------------------------------------------
98
9de6d037
MPM
99$iscgi = 0;
100
d58b8e00
MPM
101# Parse commandline
102foreach $a (@ARGV) {
103 if ($a eq '-q') {
104 $verblev--;
105 } elsif ($a eq '-v') {
106 $verblev++;
9de6d037
MPM
107 } elsif ($a eq '--cgi') {
108 $iscgi = 1;
d58b8e00
MPM
109 } else {
110 print("Unknown parameter: $a\n");
111 print("Syntax: $0 [-q] [-v]\n");
112 print(" -q decreases verbosity, -v increases verbosity.\n");
113 exit(1);
114 }
115}
116
9de6d037
MPM
117if (defined($ENV{'REQUEST_URI'})) {
118 $iscgi = 1;
6b3575c2 119 print("Content-type: application/json; charset=utf-8\n");
e79ebcd6
MPM
120 print("Access-Control-Allow-Origin: *\n");
121 print("Cache-Control: public, max-age=3600\n");
122 print("\n");
9de6d037
MPM
123 unless ($ENV{'REQUEST_URI'} =~ m!(\d+)/(\d+)/(\d+)\.json$!) {
124 print("{\n\"_comment\": \"Sorry, query parameters not understood.\"\n}\n");
125 exit(0);
126 }
127 my $cgiz = int($1); my $cgix = int($2); my $cgiy = int($3);
128 #print("DEBUG: z=$cgiz x=$cgix y=$cgiy\n");
129 if ($cgiz < 15) {
130 print("{\n\"_comment\": \"No data will be returned at this zoom level.\"\n}\n");
131 }
132 ($y1, $x1, $y2, $x2) = Project($cgix, $cgiy, $cgiz);
133 #print("DEBUG: mapped to ($x1 $y1) ($x2 $y2)\n");
134}
135
d58b8e00
MPM
136unless ($dbh = DBI->connect("dbi:Pg:dbname=$dbname","","")) {
137 print(STDERR "Failed to open database. Please try again later.\n"); exit(1);
138}
139
140$tx1 = $dbh->selectrow_array("select ST_X(ST_transform(ST_GeomFromText('POINT($x1 $y1)', 4326), 900913))");
141$ty1 = $dbh->selectrow_array("select ST_Y(ST_transform(ST_GeomFromText('POINT($x1 $y1)', 4326), 900913))");
142$tx2 = $dbh->selectrow_array("select ST_X(ST_transform(ST_GeomFromText('POINT($x2 $y2)', 4326), 900913))");
143$ty2 = $dbh->selectrow_array("select ST_Y(ST_transform(ST_GeomFromText('POINT($x2 $y2)', 4326), 900913))");
e044730a
MPM
144# Why do crappy perl libraries always insist on using an "Object Oriented" interface
145# for things where this does not make any sense at all?
146my $crappyjsonoo = JSON->new()->latin1(); # Note: has to be latin1 instead of utf8
147# for now, because the strings we get from the DB are WTF8, but are not marked
148# as such. Therefore, we must not tell the JSON module to do utf8(), because
149# otherwise it would encode the WTF8 twice.
150# This will change with DBI::Pg modules >3 where UTF8 is properly handled.
151if ($verblev > 0) {
152 $crappyjsonoo = $crappyjsonoo->pretty();
153}
d58b8e00 154my $cntr = 0;
9de6d037
MPM
155if ($iscgi == 0) {
156 print("// Note: this file has been autogenerated by $0\n");
e79ebcd6 157 printwows("var FAUGeoJSON = {\n");
9de6d037 158} else {
e79ebcd6 159 printwows("{\n");
9de6d037 160}
e79ebcd6
MPM
161printwows(" \"type\": \"FeatureCollection\",\n");
162printwows(" \"features\": [\n");
9de6d037
MPM
163foreach $xtable ('planet_osm_polygon1', 'planet_osm_polygon2', 'planet_osm_line') { # 'planet_osm_polygon', 'planet_osm_line'
164 my $querypart1 = " ((tags->'building:part') is not null)";
165 my $table = 'planet_osm_polygon';
166 if ($xtable eq 'planet_osm_polygon1') {
167 $querypart1 = " (building is not null)";
168 } elsif ($xtable eq 'planet_osm_line') {
169 $table = $xtable;
170 }
171 # Note: && in this case is the postgis operator for 'bounding box overlaps'.
d58b8e00 172 my $sth = $dbh->prepare("select osm_id, way, building, 'tower:type', name, tags"
9de6d037
MPM
173 . " from $table where $querypart1"
174 . " and (way && ST_MakeEnvelope($tx1, $ty1, $tx2, $ty2))");
d58b8e00 175 unless ($sth->execute()) {
9de6d037 176 print(STDERR "Sorry, database query blew up.!\n");
d58b8e00
MPM
177 exit(1);
178 }
179 my $sth2 = $dbh->prepare("select ST_X(points) as x, ST_Y(points) as y"
180 . " from (select ST_astext((ST_dumppoints(ST_transform(?, 4326))).geom) as points) as points");
d25e5b4e 181 my $sth4 = $dbh->prepare("select (ST_dumprings(?)).geom as rings");
d58b8e00 182 while ($row = $sth->fetchrow_hashref()) {
9de6d037
MPM
183 if ($xtable eq 'planet_osm_polygon1') {
184 my $hassubparts = $dbh->selectrow_array("select count(*)"
e044730a
MPM
185 . " from planet_osm_line"
186 . " where ((tags->'building:part') is not null)"
187 . " and (ST_Covers(" . $dbh->quote($row->{'way'}) . ", way) = true)");
9de6d037
MPM
188 # The subparts usually cover the whole building, so we do NOT draw the
189 # whole building but only the subparts.
190 if ($hassubparts > 0) { next; }
191 }
d58b8e00
MPM
192 my $hstoredec;
193 if (defined($row->{'tags'})) {
194 $hstoredec = Pg::hstore::decode($row->{'tags'});
195 }
9de6d037 196 my $levels = fetchlastofhr(1, $hstoredec, 'levels', 'building:levels');
2c78883c
MPM
197 my $height = fetchlastofhr(undef, $hstoredec, 'height', 'building:height');
198 unless ((defined($height)) && ($height =~ m/^[0-9.]+$/)) {
199 undef($height);
200 }
9de6d037
MPM
201 my $minlevel = fetchlastofhr(0, $hstoredec,
202 'min_levels', 'building:min_levels', 'min_level', 'building:min_level');
2c78883c
MPM
203 my $minheight = fetchlastofhr(undef, $hstoredec, 'min_height', 'building:min_height');
204 unless ((defined($minheight)) && ($minheight =~ m/^[0-9.]+$/)) {
205 undef($minheight);
206 }
6b3575c2
MPM
207 my $shape = fetchlastofhr(undef, $hstoredec, 'building:shape');
208 my $material = fetchlastofhr(undef, $hstoredec,
209 'building:material', 'building:facade:material', 'building:cladding');
d58b8e00
MPM
210 my $wallcolor = fetchlastofhr(undef, $hstoredec, 'building:color', 'building:colour');
211 my $roofcolor = fetchlastofhr(undef, $hstoredec,
212 'roof:color', 'roof:colour', 'building:roof:color', 'building:roof:colour');
213 my $roofshape = fetchlastofhr(undef, $hstoredec, 'roof:shape', 'building:roof:shape');
214 my $roofheight = fetchlastofhr(undef, $hstoredec, 'roof:height', 'building:roof:height');
e044730a
MPM
215 unless ((defined($roofheight)) && ($roofheight =~ m/^[0-9.]+$/)) {
216 undef($roofheight);
217 }
e79ebcd6 218 my $roofmaterial = fetchlastofhr(undef, $hstoredec, 'roof:material', 'building:roof:material');
9de6d037 219 if ($cntr != 0) {
e79ebcd6 220 printwows(",\n");
9de6d037 221 }
d58b8e00 222 $cntr++;
e044730a
MPM
223 my $jso = { "type" => "Feature" };
224 $jso->{'id'} = (defined($row->{'osm_id'}) ? $row->{'osm_id'} : $cntr);
9de6d037 225 if (defined($row->{'name'})) {
e044730a 226 $jso->{'name'} = $row->{'name'};
9de6d037 227 }
d25e5b4e
MPM
228 my @waystodecode = ();
229 {
230 local $sth4->{'PrintError'}; # We're fully aware that the execute can
231 local $sth4->{'PrintWarn'}; # fail, no need to spam about it.
232 if ($sth4->execute($row->{'way'})) {
233 while ((my $nxtway) = $sth4->fetchrow_array()) {
234 push(@waystodecode, $nxtway);
235 }
236 } else {
237 push(@waystodecode, $row->{'way'});
238 }
d58b8e00 239 }
d25e5b4e
MPM
240 my @geomarr2 = ();
241 foreach my $nxtway (@waystodecode) {
242 unless ($sth2->execute($nxtway)) {
243 print(STDERR "Sorry, decoding the way data exploded.\n");
244 exit(1);
245 }
246 my ($onex, $oney);
247 my @geomarr1 = ();
248 while (($onex, $oney) = $sth2->fetchrow_array()) {
249 my @onecoord = ( sprintf("%.5f", $onex), sprintf("%.5f", $oney));
250 push (@geomarr1, \@onecoord);
251 }
252 push(@geomarr2, \@geomarr1);
253 $sth2->finish();
d58b8e00 254 }
e044730a
MPM
255 my $geomhsh = { "type" => "Polygon",
256 "coordinates" => \@geomarr2 };
257 $jso->{'geometry'} = $geomhsh;
258 my $prophsh = { };
9de6d037 259 if (defined($height)) {
e044730a 260 $prophsh->{'height'} = $height;
9de6d037 261 }
e79ebcd6 262 if (defined($minheight)) {
e044730a 263 $prophsh->{'minHeight'} = $minheight;
9de6d037
MPM
264 }
265 if (defined($levels)) {
e044730a 266 $prophsh->{'levels'} = $levels;
9de6d037
MPM
267 }
268 if (defined($minlevel)) {
e044730a 269 $prophsh->{'minLevel'} = $minlevel;
9de6d037 270 }
6b3575c2 271 if (defined($shape)) {
e044730a 272 $prophsh->{'shape'} = $shape;
6b3575c2
MPM
273 }
274 if (defined($material)) {
e044730a 275 $prophsh->{'material'} = $material;
6b3575c2 276 }
d58b8e00 277 if (defined($wallcolor)) {
e044730a 278 $prophsh->{'wallColor'} = $wallcolor;
d58b8e00
MPM
279 }
280 if (defined($roofcolor)) {
e044730a 281 $prophsh->{'roofColor'} = $roofcolor;
d58b8e00
MPM
282 }
283 if (defined($roofshape)) {
e79ebcd6 284 if ($roofshape eq 'pyramidal') { $roofshape = 'pyramid'; }
e044730a 285 $prophsh->{'roofShape'} = $roofshape;
e79ebcd6
MPM
286 }
287 if (defined($roofmaterial)) {
e044730a 288 $prophsh->{'roofMaterial'} = $roofmaterial;
d58b8e00
MPM
289 }
290 if (defined($roofheight)) {
e044730a 291 $prophsh->{'roofHeight'} = $roofheight;
d58b8e00 292 }
e044730a
MPM
293 $jso->{'properties'} = $prophsh;
294 # Now dump it all.
295 print($crappyjsonoo->encode($jso));
d58b8e00 296 }
d25e5b4e
MPM
297 $sth4->finish();
298 undef($sth4);
d58b8e00
MPM
299 $sth->finish();
300 undef($sth);
301}
e79ebcd6 302printwows("\n ]\n");
9de6d037
MPM
303if ($iscgi == 0) {
304 print("};\n");
305} else {
306 print("}\n");
307}
This page took 0.468461 seconds and 4 git commands to generate.