fix my double-escape-FAIL
[osmrrze.git] / scripts / osmbuildings-json-generator.pl
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
18 use DBI;
19 use POSIX qw(strftime mktime);
20 use Pg::hstore;
21 use JSON;
22
23 # Par. 0: Level on which this gets printed
24 # Par. 1: Text
25 sub 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.
43 sub 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
55 # Helper functions for the CGI variant. Taken from the openstreetmap-Wiki.
56 use Math::Trig;
57 sub Project {
58   my ($X, $Y, $Zoom) = @_;
59   my $Unit = 1 / (2 ** $Zoom);
60   my $relY1 = $Y * $Unit;
61   my $relY2 = $relY1 + $Unit;
62  
63   # note: $LimitY = ProjectF(degrees(atan(sinh(pi)))) = log(sinh(pi)+cosh(pi)) = pi
64   # note: degrees(atan(sinh(pi))) = 85.051128..
65   #my $LimitY = ProjectF(85.0511);
66  
67   # so stay simple and more accurate
68   my $LimitY = pi;
69   my $RangeY = 2 * $LimitY;
70   $relY1 = $LimitY - $RangeY * $relY1;
71   $relY2 = $LimitY - $RangeY * $relY2;
72   my $Lat1 = ProjectMercToLat($relY1);
73   my $Lat2 = ProjectMercToLat($relY2);
74   $Unit = 360 / (2 ** $Zoom);
75   my $Long1 = -180 + $X * $Unit;
76   return ($Lat2, $Long1, $Lat1, $Long1 + $Unit); # S,W,N,E
77 }
78 sub ProjectMercToLat($){
79   my $MercY = shift;
80   return rad2deg(atan(sinh($MercY)));
81 }
82 sub ProjectF
83 {
84   my $Lat = shift;
85   $Lat = deg2rad($Lat);
86   my $Y = log(tan($Lat) + sec($Lat));
87   return $Y;
88 }
89
90 # print that drops whitespace when not in verbose mode.
91 sub printwows($) {
92   my $res = $_[0];
93   if ($verblev > 0) {
94     print($res); return;
95   }
96   $res =~ s/^\s+//g;
97   $res =~ s/"\s*:\s*/":/g;
98   $res =~ s/[\r\n]//g;
99   print($res);
100 }
101
102 # ----------------------------------------------------------------------------
103 # main()
104 # ----------------------------------------------------------------------------
105
106 $iscgi = 0;
107
108 # Parse commandline
109 foreach $a (@ARGV) {
110   if ($a eq '-q') {
111     $verblev--;
112   } elsif ($a eq '-v') {
113     $verblev++;
114   } elsif ($a eq '--cgi') {
115     $iscgi = 1;
116   } else {
117     print("Unknown parameter: $a\n");
118     print("Syntax: $0 [-q] [-v]\n");
119     print(" -q decreases verbosity, -v increases verbosity.\n");
120     exit(1);
121   }
122 }
123
124 if (defined($ENV{'REQUEST_URI'})) {
125   $iscgi = 1;
126   print("Content-type: application/json; charset=utf-8\n");
127   print("Access-Control-Allow-Origin: *\n");
128   print("Cache-Control: public, max-age=3600\n");
129   print("\n");
130   unless ($ENV{'REQUEST_URI'} =~ m!(\d+)/(\d+)/(\d+)\.json$!) {
131     print("{\n\"_comment\": \"Sorry, query parameters not understood.\"\n}\n");
132     exit(0);
133   }
134   my $cgiz = int($1); my $cgix = int($2); my $cgiy = int($3);
135   #print("DEBUG: z=$cgiz x=$cgix y=$cgiy\n");
136   if ($cgiz < 15) {
137     print("{\n\"_comment\": \"No data will be returned at this zoom level.\"\n}\n");
138   }
139   ($y1, $x1, $y2, $x2) = Project($cgix, $cgiy, $cgiz);
140   #print("DEBUG: mapped to ($x1 $y1) ($x2 $y2)\n");
141 }
142
143 unless ($dbh = DBI->connect("dbi:Pg:dbname=$dbname","","")) {
144   print(STDERR "Failed to open database. Please try again later.\n"); exit(1);
145 }
146
147 $tx1 = $dbh->selectrow_array("select ST_X(ST_transform(ST_GeomFromText('POINT($x1 $y1)', 4326), 900913))");
148 $ty1 = $dbh->selectrow_array("select ST_Y(ST_transform(ST_GeomFromText('POINT($x1 $y1)', 4326), 900913))");
149 $tx2 = $dbh->selectrow_array("select ST_X(ST_transform(ST_GeomFromText('POINT($x2 $y2)', 4326), 900913))");
150 $ty2 = $dbh->selectrow_array("select ST_Y(ST_transform(ST_GeomFromText('POINT($x2 $y2)', 4326), 900913))");
151 my $cntr = 0;
152 if ($iscgi == 0) {
153   print("// Note: this file has been autogenerated by $0\n");
154   printwows("var FAUGeoJSON = {\n");
155 } else {
156   printwows("{\n");
157 }
158 printwows("  \"type\": \"FeatureCollection\",\n");
159 printwows("  \"features\": [\n");
160 foreach $xtable ('planet_osm_polygon1', 'planet_osm_polygon2', 'planet_osm_line') { # 'planet_osm_polygon', 'planet_osm_line'
161   my $querypart1 = " ((tags->'building:part') is not null)";
162   my $table = 'planet_osm_polygon'; 
163   if ($xtable eq 'planet_osm_polygon1') {
164     $querypart1 = " (building is not null)";
165   } elsif ($xtable eq 'planet_osm_line') {
166     $table = $xtable;
167   }
168   # Note: && in this case is the postgis operator for 'bounding box overlaps'.
169   my $sth = $dbh->prepare("select osm_id, way, building, 'tower:type', name, tags"
170                         . " from $table where $querypart1"
171                         . "   and (way && ST_MakeEnvelope($tx1, $ty1, $tx2, $ty2))");
172   unless ($sth->execute()) {
173     print(STDERR "Sorry, database query blew up.!\n");
174     exit(1);
175   }
176   my $sth2 = $dbh->prepare("select ST_X(points) as x, ST_Y(points) as y"
177                          . " from (select ST_astext((ST_dumppoints(ST_transform(?, 4326))).geom) as points) as points");
178   while ($row = $sth->fetchrow_hashref()) {
179     if ($xtable eq 'planet_osm_polygon1') {
180       my $hassubparts = $dbh->selectrow_array("select count(*)"
181                                           . " from planet_osm_line"
182                                           . " where ((tags->'building:part') is not null)"
183                                           . " and (ST_Covers(" . $dbh->quote($row->{'way'}) . ", way) = true)");
184       # The subparts usually cover the whole building, so we do NOT draw the
185       # whole building but only the subparts.
186       if ($hassubparts > 0) { next; }
187     }
188     my $hstoredec;
189     if (defined($row->{'tags'})) {
190       $hstoredec = Pg::hstore::decode($row->{'tags'});
191     }
192     my $levels = fetchlastofhr(1, $hstoredec, 'levels', 'building:levels');
193     my $height = fetchlastofhr(undef, $hstoredec, 'height', 'building:height');
194     unless ((defined($height)) && ($height =~ m/^[0-9.]+$/)) {
195       undef($height);
196     }
197     my $minlevel = fetchlastofhr(0, $hstoredec,
198                                  'min_levels', 'building:min_levels', 'min_level', 'building:min_level');
199     my $minheight = fetchlastofhr(undef, $hstoredec, 'min_height', 'building:min_height');
200     unless ((defined($minheight)) && ($minheight =~ m/^[0-9.]+$/)) {
201       undef($minheight);
202     }
203     my $shape = fetchlastofhr(undef, $hstoredec, 'building:shape');
204     my $material = fetchlastofhr(undef, $hstoredec,
205                                  'building:material', 'building:facade:material', 'building:cladding');
206     my $wallcolor = fetchlastofhr(undef, $hstoredec, 'building:color', 'building:colour');
207     my $roofcolor = fetchlastofhr(undef, $hstoredec,
208                                   'roof:color', 'roof:colour', 'building:roof:color', 'building:roof:colour');
209     my $roofshape = fetchlastofhr(undef, $hstoredec, 'roof:shape', 'building:roof:shape');
210     my $roofheight = fetchlastofhr(undef, $hstoredec, 'roof:height', 'building:roof:height');
211     my $roofmaterial = fetchlastofhr(undef, $hstoredec, 'roof:material', 'building:roof:material');
212     if ($cntr != 0) {
213       printwows(",\n");
214     }
215     $cntr++;
216     printwows("  {\n");
217     printwows("    \"type\": \"Feature\",\n");
218     if (defined($row->{'osm_id'})) {
219       printwows("    \"id\": " . $row->{'osm_id'} . ",\n");
220     } else {
221       printwows("    \"id\": $cntr,\n");
222     }
223     if (defined($row->{'name'})) {
224       $row->{'name'} =~ s/"//g;
225       printwows("    \"name\": \"" . $row->{'name'} . "\",\n");
226     }
227     printwows("    \"geometry\": {\n");
228     printwows("      \"type\": \"Polygon\",\n");
229     printwows("      \"coordinates\": [[\n");
230     unless ($sth2->execute($row->{'way'})) {
231       print(STDERR "Sorry, decoding the way data exploded.\n");
232       exit(1);
233     }
234     my ($onex, $oney);
235     my $firstcoord = 1;
236     while (($onex, $oney) = $sth2->fetchrow_array()) {
237       if ($firstcoord == 1) {
238         $firstcoord = 0;
239       } else {
240         printwows(",\n");
241       }
242       printwows(sprintf("        [ %.5f, %.5f ]", $onex, $oney));
243     }
244     printwows("\n");
245     printwows("      ]]\n");
246     printwows("    },\n");
247     printwows("    \"properties\": {\n");
248     if (defined($height)) {
249       printwows("       \"height\": $height,\n");
250     }
251     if (defined($minheight)) {
252       printwows("       \"minHeight\": $minheight,\n");
253     }
254     if (defined($levels)) {
255       printwows("       \"levels\": $levels,\n");
256     }
257     if (defined($minlevel)) {
258       printwows("       \"minLevel\": $minlevel,\n");
259     }
260     if (defined($shape)) {
261       $shape = JSON->new->allow_nonref->encode($shape);
262       printwows("       \"shape\": $shape,\n");
263     }
264     if (defined($material)) {
265       $material = JSON->new->allow_nonref->encode($material);
266       printwows("       \"material\": $material,\n");
267     }
268     if (defined($wallcolor)) {
269       $wallcolor = JSON->new->allow_nonref->encode($wallcolor);
270       printwows("       \"wallColor\": $wallcolor,\n");
271     }
272     if (defined($roofcolor)) {
273       $roofcolor = JSON->new->allow_nonref->encode($roofcolor);
274       printwows("       \"roofColor\": $roofcolor,\n");
275     }
276     if (defined($roofshape)) {
277       if ($roofshape eq 'pyramidal') { $roofshape = 'pyramid'; }
278       $roofshape = JSON->new->allow_nonref->encode($roofshape);
279       printwows("       \"roofShape\": $roofshape,\n");
280     }
281     if (defined($roofmaterial)) {
282       $roofmaterial = JSON->new->allow_nonref->encode($roofmaterial);
283       printwows("       \"roofMaterial\": $roofmaterial,\n");
284     }
285     if (defined($roofheight)) {
286       $roofheight = sprintf("%.1f", $roofheight);
287       printwows("       \"roofHeight\": \"$roofheight\",\n");
288     }
289     # this line is completely useless, but we need it to guarantee JSON has
290     # no trailing comma. osmbuildings will accept JSON with trailing commas
291     # just fine when you give it static, but NOT when requesting it dynamically.
292     printwows("       \"K9\": \"\"\n");
293     printwows("    }\n");
294     printwows("  }");
295   }
296   $sth2->finish();
297   undef($sth2);
298   $sth->finish();
299   undef($sth);
300 }
301 printwows("\n  ]\n");
302 if ($iscgi == 0) {
303   print("};\n");
304 } else {
305   print("}\n");
306 }
This page took 0.08455 seconds and 3 git commands to generate.