6 # Default verbosity level? Can be increased with -v, decreased with -q
9 # The area which we cover
15 # There should be no need to touch anything below this line.
16 # ---------------------------------------------------------------------------
19 use POSIX qw(strftime mktime);
23 # Par. 0: Level on which this gets printed
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())));
32 if ($_[1] =~ m/\n$/) {
33 $printlev_atbeginofline = 1;
35 $printlev_atbeginofline = 0;
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++) {
48 if (defined($hr->{$vn})) {
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
60 my ($X, $Y, $Zoom) = @_;
61 my $Unit = 1 / (2 ** $Zoom);
62 my $relY1 = $Y * $Unit;
63 my $relY2 = $relY1 + $Unit;
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
74 sub ProjectMercToLat($) {
75 return rad2deg(atan(sinh($_[0])));
78 $Lat = deg2rad($_[0]);
79 return (log(tan($Lat) + sec($Lat)));
81 # ----- end of copied helper functions -----
83 # print that drops whitespace when not in verbose mode.
90 $res =~ s/"\s*:\s*/":/g;
95 # ----------------------------------------------------------------------------
97 # ----------------------------------------------------------------------------
105 } elsif ($a eq '-v') {
107 } elsif ($a eq '--cgi') {
110 print("Unknown parameter: $a\n");
111 print("Syntax: $0 [-q] [-v]\n");
112 print(" -q decreases verbosity, -v increases verbosity.\n");
117 if (defined($ENV{'REQUEST_URI'})) {
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");
123 unless ($ENV{'REQUEST_URI'} =~ m!(\d+)/(\d+)/(\d+)\.json$!) {
124 print("{\n\"_comment\": \"Sorry, query parameters not understood.\"\n}\n");
127 my $cgiz = int($1); my $cgix = int($2); my $cgiy = int($3);
128 #print("DEBUG: z=$cgiz x=$cgix y=$cgiy\n");
130 print("{\n\"_comment\": \"No data will be returned at this zoom level.\"\n}\n");
132 ($y1, $x1, $y2, $x2) = Project($cgix, $cgiy, $cgiz);
133 #print("DEBUG: mapped to ($x1 $y1) ($x2 $y2)\n");
136 unless ($dbh = DBI->connect("dbi:Pg:dbname=$dbname","","")) {
137 print(STDERR "Failed to open database. Please try again later.\n"); exit(1);
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.
152 $crappyjsonoo = $crappyjsonoo->pretty();
156 print("// Note: this file has been autogenerated by $0\n");
157 printwows("var FAUGeoJSON = {\n");
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') {
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");
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; }
192 if (defined($row->{'tags'})) {
193 $hstoredec = Pg::hstore::decode($row->{'tags'});
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.]+$/)) {
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.]+$/)) {
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.]+$/)) {
217 my $roofmaterial = fetchlastofhr(undef, $hstoredec, 'roof:material', 'building:roof:material');
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'};
227 unless ($sth2->execute($row->{'way'})) {
228 print(STDERR "Sorry, decoding the way data exploded.\n");
233 while (($onex, $oney) = $sth2->fetchrow_array()) {
234 my @onecoord = ( sprintf("%.5f", $onex), sprintf("%.5f", $oney));
235 push (@geomarr1, \@onecoord);
237 my @geomarr2 = (\@geomarr1);
238 my $geomhsh = { "type" => "Polygon",
239 "coordinates" => \@geomarr2 };
240 $jso->{'geometry'} = $geomhsh;
242 if (defined($height)) {
243 $prophsh->{'height'} = $height;
245 if (defined($minheight)) {
246 $prophsh->{'minHeight'} = $minheight;
248 if (defined($levels)) {
249 $prophsh->{'levels'} = $levels;
251 if (defined($minlevel)) {
252 $prophsh->{'minLevel'} = $minlevel;
254 if (defined($shape)) {
255 $prophsh->{'shape'} = $shape;
257 if (defined($material)) {
258 $prophsh->{'material'} = $material;
260 if (defined($wallcolor)) {
261 $prophsh->{'wallColor'} = $wallcolor;
263 if (defined($roofcolor)) {
264 $prophsh->{'roofColor'} = $roofcolor;
266 if (defined($roofshape)) {
267 if ($roofshape eq 'pyramidal') { $roofshape = 'pyramid'; }
268 $prophsh->{'roofShape'} = $roofshape;
270 if (defined($roofmaterial)) {
271 $prophsh->{'roofMaterial'} = $roofmaterial;
273 if (defined($roofheight)) {
274 $prophsh->{'roofHeight'} = $roofheight;
276 $jso->{'properties'} = $prophsh;
278 print($crappyjsonoo->encode($jso));