| 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 | 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 | } |