5cd216f370c7e975a1c6f5dde7c327a9ca8dd965
[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()->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.
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(?, 4326))).geom) as points) as points");
181   while ($row = $sth->fetchrow_hashref()) {
182     if ($xtable eq 'planet_osm_polygon1') {
183       my $hassubparts = $dbh->selectrow_array("select count(*)"
184                                             . " from planet_osm_line"
185                                             . " where ((tags->'building:part') is not null)"
186                                             . " and (ST_Covers(" . $dbh->quote($row->{'way'}) . ", way) = true)");
187       # The subparts usually cover the whole building, so we do NOT draw the
188       # whole building but only the subparts.
189       if ($hassubparts > 0) { next; }
190     }
191     my $hstoredec;
192     if (defined($row->{'tags'})) {
193       $hstoredec = Pg::hstore::decode($row->{'tags'});
194     }
195     my $levels = fetchlastofhr(1, $hstoredec, 'levels', 'building:levels');
196     my $height = fetchlastofhr(undef, $hstoredec, 'height', 'building:height');
197     unless ((defined($height)) && ($height =~ m/^[0-9.]+$/)) {
198       undef($height);
199     }
200     my $minlevel = fetchlastofhr(0, $hstoredec,
201                                  'min_levels', 'building:min_levels', 'min_level', 'building:min_level');
202     my $minheight = fetchlastofhr(undef, $hstoredec, 'min_height', 'building:min_height');
203     unless ((defined($minheight)) && ($minheight =~ m/^[0-9.]+$/)) {
204       undef($minheight);
205     }
206     my $shape = fetchlastofhr(undef, $hstoredec, 'building:shape');
207     my $material = fetchlastofhr(undef, $hstoredec,
208                                  'building:material', 'building:facade:material', 'building:cladding');
209     my $wallcolor = fetchlastofhr(undef, $hstoredec, 'building:color', 'building:colour');
210     my $roofcolor = fetchlastofhr(undef, $hstoredec,
211                                   'roof:color', 'roof:colour', 'building:roof:color', 'building:roof:colour');
212     my $roofshape = fetchlastofhr(undef, $hstoredec, 'roof:shape', 'building:roof:shape');
213     my $roofheight = fetchlastofhr(undef, $hstoredec, 'roof:height', 'building:roof:height');
214     unless ((defined($roofheight)) && ($roofheight =~ m/^[0-9.]+$/)) {
215       undef($roofheight);
216     }
217     my $roofmaterial = fetchlastofhr(undef, $hstoredec, 'roof:material', 'building:roof:material');
218     if ($cntr != 0) {
219       printwows(",\n");
220     }
221     $cntr++;
222     my $jso = { "type" => "Feature" };
223     $jso->{'id'} = (defined($row->{'osm_id'}) ? $row->{'osm_id'} : $cntr);
224     if (defined($row->{'name'})) {
225       $jso->{'name'} = $row->{'name'};
226     }
227     unless ($sth2->execute($row->{'way'})) {
228       print(STDERR "Sorry, decoding the way data exploded.\n");
229       exit(1);
230     }
231     my ($onex, $oney);
232     my @geomarr1 = ();
233     while (($onex, $oney) = $sth2->fetchrow_array()) {
234       my @onecoord = ( sprintf("%.5f", $onex), sprintf("%.5f", $oney));
235       push (@geomarr1, \@onecoord);
236     }
237     my @geomarr2 = (\@geomarr1);
238     my $geomhsh = { "type" => "Polygon",
239                     "coordinates" => \@geomarr2 };
240     $jso->{'geometry'} = $geomhsh;
241     my $prophsh = { };
242     if (defined($height)) {
243       $prophsh->{'height'} = $height;
244     }
245     if (defined($minheight)) {
246       $prophsh->{'minHeight'} = $minheight;
247     }
248     if (defined($levels)) {
249       $prophsh->{'levels'} = $levels;
250     }
251     if (defined($minlevel)) {
252       $prophsh->{'minLevel'} = $minlevel;
253     }
254     if (defined($shape)) {
255       $prophsh->{'shape'} = $shape;
256     }
257     if (defined($material)) {
258       $prophsh->{'material'} = $material;
259     }
260     if (defined($wallcolor)) {
261       $prophsh->{'wallColor'} = $wallcolor;
262     }
263     if (defined($roofcolor)) {
264       $prophsh->{'roofColor'} = $roofcolor;
265     }
266     if (defined($roofshape)) {
267       if ($roofshape eq 'pyramidal') { $roofshape = 'pyramid'; }
268       $prophsh->{'roofShape'} = $roofshape;
269     }
270     if (defined($roofmaterial)) {
271       $prophsh->{'roofMaterial'} = $roofmaterial;
272     }
273     if (defined($roofheight)) {
274       $prophsh->{'roofHeight'} = $roofheight;
275     }
276     $jso->{'properties'} = $prophsh;
277     # Now dump it all.
278     print($crappyjsonoo->encode($jso));
279   }
280   $sth2->finish();
281   undef($sth2);
282   $sth->finish();
283   undef($sth);
284 }
285 printwows("\n  ]\n");
286 if ($iscgi == 0) {
287   print("};\n");
288 } else {
289   print("}\n");
290 }
This page took 0.081636 seconds and 2 git commands to generate.