d0f5a986fb11289194f974420dd2b396b5305aff
[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 # which in turn is almost a direct copy of a GPL'd script included in (amongst
57 # others) the mapnik sourcecode. -> GPL code
58 use Math::Trig;
59 sub Project($$$) {
60   my ($X, $Y, $Zoom) = @_;
61   my $Unit = 1 / (2 ** $Zoom);
62   my $relY1 = $Y * $Unit;
63   my $relY2 = $relY1 + $Unit;
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 }
74 sub ProjectMercToLat($) {
75   return rad2deg(atan(sinh($_[0])));
76 }
77 sub ProjectF($) {
78   $Lat = deg2rad($_[0]);
79   return (log(tan($Lat) + sec($Lat)));
80 }
81 # ----- end of copied helper functions -----
82
83 # print that drops whitespace when not in verbose mode.
84 sub 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
95 # ----------------------------------------------------------------------------
96 # main()
97 # ----------------------------------------------------------------------------
98
99 $iscgi = 0;
100
101 # Parse commandline
102 foreach $a (@ARGV) {
103   if ($a eq '-q') {
104     $verblev--;
105   } elsif ($a eq '-v') {
106     $verblev++;
107   } elsif ($a eq '--cgi') {
108     $iscgi = 1;
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
117 if (defined($ENV{'REQUEST_URI'})) {
118   $iscgi = 1;
119   print("Content-type: application/json; charset=utf-8\n");
120   print("Access-Control-Allow-Origin: *\n");
121   print("Cache-Control: public, max-age=3600\n");
122   print("\n");
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
136 unless ($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))");
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?
146 my $crappyjsonoo = JSON->new()->utf8(); # Note: This has to be latin1 instead of
147 # utf8 for DBI::Pg modules with version <3, because in that case, the strings we
148 # get from the DB are WTF8, but are not marked as such. Therefore, we must not
149 # tell the JSON module to do utf8(), because otherwise it would encode the WTF8
150 # twice.
151 if ($verblev > 0) {
152   $crappyjsonoo = $crappyjsonoo->pretty();
153 }
154 my $cntr = 0;
155 if ($iscgi == 0) {
156   print("// Note: this file has been autogenerated by $0\n");
157   printwows("var FAUGeoJSON = {\n");
158 } else {
159   printwows("{\n");
160 }
161 printwows("  \"type\": \"FeatureCollection\",\n");
162 printwows("  \"features\": [\n");
163 foreach $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'.
172   my $sth = $dbh->prepare("select osm_id, way, building, 'tower:type', name, tags"
173                         . " from $table where $querypart1"
174                         . "   and (way && ST_MakeEnvelope($tx1, $ty1, $tx2, $ty2))");
175   unless ($sth->execute()) {
176     print(STDERR "Sorry, database query blew up.!\n");
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(geometry(?), 4326))).geom) as points) as points");
181   my $sth4 = $dbh->prepare("select (ST_dumprings(?)).geom as rings");
182   while ($row = $sth->fetchrow_hashref()) {
183     if ($xtable eq 'planet_osm_polygon1') {
184       my $hassubparts = $dbh->selectrow_array("select count(*)"
185                                             . " from planet_osm_line"
186                                             . " where ((tags->'building:part') is not null)"
187                                             . " and (ST_Covers(" . $dbh->quote($row->{'way'}) . ", way) = true)");
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     }
192     my $hstoredec;
193     if (defined($row->{'tags'})) {
194       $hstoredec = Pg::hstore::decode($row->{'tags'});
195     }
196     my $levels = fetchlastofhr(1, $hstoredec, 'levels', 'building:levels');
197     my $height = fetchlastofhr(undef, $hstoredec, 'height', 'building:height');
198     unless ((defined($height)) && ($height =~ m/^[0-9.]+$/)) {
199       undef($height);
200     }
201     my $minlevel = fetchlastofhr(0, $hstoredec,
202                                  'min_levels', 'building:min_levels', 'min_level', 'building:min_level');
203     my $minheight = fetchlastofhr(undef, $hstoredec, 'min_height', 'building:min_height');
204     unless ((defined($minheight)) && ($minheight =~ m/^[0-9.]+$/)) {
205       undef($minheight);
206     }
207     my $shape = fetchlastofhr(undef, $hstoredec, 'building:shape');
208     my $material = fetchlastofhr(undef, $hstoredec,
209                                  'building:material', 'building:facade:material', 'building:cladding');
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');
215     unless ((defined($roofheight)) && ($roofheight =~ m/^[0-9.]+$/)) {
216       undef($roofheight);
217     }
218     my $roofmaterial = fetchlastofhr(undef, $hstoredec, 'roof:material', 'building:roof:material');
219     if ($cntr != 0) {
220       printwows(",\n");
221     }
222     $cntr++;
223     my $jso = { "type" => "Feature" };
224     $jso->{'id'} = (defined($row->{'osm_id'}) ? $row->{'osm_id'} : $cntr);
225     if (defined($row->{'name'})) {
226       $jso->{'name'} = $row->{'name'};
227     }
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       }
239     }
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();
254     }
255     my $geomhsh = { "type" => "Polygon",
256                     "coordinates" => \@geomarr2 };
257     $jso->{'geometry'} = $geomhsh;
258     my $prophsh = { };
259     if (defined($height)) {
260       $prophsh->{'height'} = $height;
261     }
262     if (defined($minheight)) {
263       $prophsh->{'minHeight'} = $minheight;
264     }
265     if (defined($levels)) {
266       $prophsh->{'levels'} = $levels;
267     }
268     if (defined($minlevel)) {
269       $prophsh->{'minLevel'} = $minlevel;
270     }
271     if (defined($shape)) {
272       $prophsh->{'shape'} = $shape;
273     }
274     if (defined($material)) {
275       $prophsh->{'material'} = $material;
276     }
277     if (defined($wallcolor)) {
278       $prophsh->{'wallColor'} = $wallcolor;
279     }
280     if (defined($roofcolor)) {
281       $prophsh->{'roofColor'} = $roofcolor;
282     }
283     if (defined($roofshape)) {
284       if ($roofshape eq 'pyramidal') { $roofshape = 'pyramid'; }
285       $prophsh->{'roofShape'} = $roofshape;
286     }
287     if (defined($roofmaterial)) {
288       $prophsh->{'roofMaterial'} = $roofmaterial;
289     }
290     if (defined($roofheight)) {
291       $prophsh->{'roofHeight'} = $roofheight;
292     }
293     $jso->{'properties'} = $prophsh;
294     # Now dump it all.
295     print($crappyjsonoo->encode($jso));
296   }
297   $sth4->finish();
298   undef($sth4);
299   $sth->finish();
300   undef($sth);
301 }
302 printwows("\n  ]\n");
303 if ($iscgi == 0) {
304   print("};\n");
305 } else {
306   print("}\n");
307 }
This page took 0.055898 seconds and 4 git commands to generate.