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);
22 # Par. 0: Level on which this gets printed
25 unless (defined($printlev_atbeginofline)) { $printlev_atbeginofline = 1; }
26 if ($verblev >= $_[0]) {
27 if ($printlev_atbeginofline) {
28 print(strftime("[%Y%m%d-%H%M%S] ", localtime(time())));
31 if ($_[1] =~ m/\n$/) {
32 $printlev_atbeginofline = 1;
34 $printlev_atbeginofline = 0;
39 # Par. 0: default value to return if nothing is defined
40 # Par. 1: the hashref containing our variables.
41 # Par. 2: variables to query. The LAST one that exists will be returned.
42 sub fetchlastofhr($$@) {
43 my $res; my $hr; my @vns;
44 ($res, $hr, @vns) = @_;
45 for (my $i = 0; $i < @vns; $i++) {
47 if (defined($hr->{$vn})) {
54 # Helper functions for the CGI variant. Taken from the openstreetmap-Wiki.
57 my ($X, $Y, $Zoom) = @_;
58 my $Unit = 1 / (2 ** $Zoom);
59 my $relY1 = $Y * $Unit;
60 my $relY2 = $relY1 + $Unit;
62 # note: $LimitY = ProjectF(degrees(atan(sinh(pi)))) = log(sinh(pi)+cosh(pi)) = pi
63 # note: degrees(atan(sinh(pi))) = 85.051128..
64 #my $LimitY = ProjectF(85.0511);
66 # so stay simple and more accurate
68 my $RangeY = 2 * $LimitY;
69 $relY1 = $LimitY - $RangeY * $relY1;
70 $relY2 = $LimitY - $RangeY * $relY2;
71 my $Lat1 = ProjectMercToLat($relY1);
72 my $Lat2 = ProjectMercToLat($relY2);
73 $Unit = 360 / (2 ** $Zoom);
74 my $Long1 = -180 + $X * $Unit;
75 return ($Lat2, $Long1, $Lat1, $Long1 + $Unit); # S,W,N,E
77 sub ProjectMercToLat($){
79 return rad2deg(atan(sinh($MercY)));
85 my $Y = log(tan($Lat) + sec($Lat));
89 # print that drops whitespace when not in verbose mode.
96 $res =~ s/"\s*:\s*/":/g;
101 # ----------------------------------------------------------------------------
103 # ----------------------------------------------------------------------------
111 } elsif ($a eq '-v') {
113 } elsif ($a eq '--cgi') {
116 print("Unknown parameter: $a\n");
117 print("Syntax: $0 [-q] [-v]\n");
118 print(" -q decreases verbosity, -v increases verbosity.\n");
123 if (defined($ENV{'REQUEST_URI'})) {
125 print("Content-type: application/json\n");
126 print("Access-Control-Allow-Origin: *\n");
127 print("Cache-Control: public, max-age=3600\n");
129 unless ($ENV{'REQUEST_URI'} =~ m!(\d+)/(\d+)/(\d+)\.json$!) {
130 print("{\n\"_comment\": \"Sorry, query parameters not understood.\"\n}\n");
133 my $cgiz = int($1); my $cgix = int($2); my $cgiy = int($3);
134 #print("DEBUG: z=$cgiz x=$cgix y=$cgiy\n");
136 print("{\n\"_comment\": \"No data will be returned at this zoom level.\"\n}\n");
138 ($y1, $x1, $y2, $x2) = Project($cgix, $cgiy, $cgiz);
139 #print("DEBUG: mapped to ($x1 $y1) ($x2 $y2)\n");
142 unless ($dbh = DBI->connect("dbi:Pg:dbname=$dbname","","")) {
143 print(STDERR "Failed to open database. Please try again later.\n"); exit(1);
146 $tx1 = $dbh->selectrow_array("select ST_X(ST_transform(ST_GeomFromText('POINT($x1 $y1)', 4326), 900913))");
147 $ty1 = $dbh->selectrow_array("select ST_Y(ST_transform(ST_GeomFromText('POINT($x1 $y1)', 4326), 900913))");
148 $tx2 = $dbh->selectrow_array("select ST_X(ST_transform(ST_GeomFromText('POINT($x2 $y2)', 4326), 900913))");
149 $ty2 = $dbh->selectrow_array("select ST_Y(ST_transform(ST_GeomFromText('POINT($x2 $y2)', 4326), 900913))");
152 print("// Note: this file has been autogenerated by $0\n");
153 printwows("var FAUGeoJSON = {\n");
157 printwows(" \"type\": \"FeatureCollection\",\n");
158 printwows(" \"features\": [\n");
159 foreach $xtable ('planet_osm_polygon1', 'planet_osm_polygon2', 'planet_osm_line') { # 'planet_osm_polygon', 'planet_osm_line'
160 my $querypart1 = " ((tags->'building:part') is not null)";
161 my $table = 'planet_osm_polygon';
162 if ($xtable eq 'planet_osm_polygon1') {
163 $querypart1 = " (building is not null)";
164 } elsif ($xtable eq 'planet_osm_line') {
167 # Note: && in this case is the postgis operator for 'bounding box overlaps'.
168 my $sth = $dbh->prepare("select osm_id, way, building, 'tower:type', name, tags"
169 . " from $table where $querypart1"
170 . " and (way && ST_MakeEnvelope($tx1, $ty1, $tx2, $ty2))");
171 unless ($sth->execute()) {
172 print(STDERR "Sorry, database query blew up.!\n");
175 my $sth2 = $dbh->prepare("select ST_X(points) as x, ST_Y(points) as y"
176 . " from (select ST_astext((ST_dumppoints(ST_transform(?, 4326))).geom) as points) as points");
177 while ($row = $sth->fetchrow_hashref()) {
178 if ($xtable eq 'planet_osm_polygon1') {
179 my $hassubparts = $dbh->selectrow_array("select count(*)"
180 . " from planet_osm_line"
181 . " where ((tags->'building:part') is not null)"
182 . " and (ST_Covers(" . $dbh->quote($row->{'way'}) . ", way) = true)");
183 # The subparts usually cover the whole building, so we do NOT draw the
184 # whole building but only the subparts.
185 if ($hassubparts > 0) { next; }
188 if (defined($row->{'tags'})) {
189 $hstoredec = Pg::hstore::decode($row->{'tags'});
191 my $levels = fetchlastofhr(1, $hstoredec, 'levels', 'building:levels');
192 my $height = fetchlastofhr($height, $hstoredec, 'height', 'building:height');
193 unless ($height =~ m/^[0-9.]+$/) { undef($height); }
194 my $minlevel = fetchlastofhr(0, $hstoredec,
195 'min_levels', 'building:min_levels', 'min_level', 'building:min_level');
196 my $minheight = fetchlastofhr($minheight, $hstoredec, 'min_height', 'building:min_height');
197 unless ($minheight =~ m/^[0-9.]+$/) { undef($minheight); }
198 my $wallcolor = fetchlastofhr(undef, $hstoredec, 'building:color', 'building:colour');
199 my $roofcolor = fetchlastofhr(undef, $hstoredec,
200 'roof:color', 'roof:colour', 'building:roof:color', 'building:roof:colour');
201 my $roofshape = fetchlastofhr(undef, $hstoredec, 'roof:shape', 'building:roof:shape');
202 my $roofheight = fetchlastofhr(undef, $hstoredec, 'roof:height', 'building:roof:height');
203 my $roofmaterial = fetchlastofhr(undef, $hstoredec, 'roof:material', 'building:roof:material');
209 printwows(" \"type\": \"Feature\",\n");
210 if (defined($row->{'osm_id'})) {
211 printwows(" \"id\": " . $row->{'osm_id'} . ",\n");
213 printwows(" \"id\": $cntr,\n");
215 if (defined($row->{'name'})) {
216 $row->{'name'} =~ s/"//g;
217 printwows(" \"name\": \"" . $row->{'name'} . "\",\n");
219 printwows(" \"geometry\": {\n");
220 printwows(" \"type\": \"Polygon\",\n");
221 printwows(" \"coordinates\": [[\n");
222 unless ($sth2->execute($row->{'way'})) {
223 print(STDERR "Sorry, decoding the way data exploded.\n");
228 while (($onex, $oney) = $sth2->fetchrow_array()) {
229 if ($firstcoord == 1) {
234 printwows(sprintf(" [ %.5f, %.5f ]", $onex, $oney));
239 printwows(" \"properties\": {\n");
240 if (defined($height)) {
241 printwows(" \"height\": $height,\n");
243 if (defined($minheight)) {
244 printwows(" \"minHeight\": $minheight,\n");
246 if (defined($levels)) {
247 printwows(" \"levels\": $levels,\n");
249 if (defined($minlevel)) {
250 printwows(" \"minLevel\": $minlevel,\n");
252 if (defined($wallcolor)) {
253 printwows(" \"wallColor\": \"$wallcolor\",\n");
255 if (defined($roofcolor)) {
256 $roofcolor =~ s/"//g;
257 printwows(" \"roofColor\": \"$roofcolor\",\n");
259 if (defined($roofshape)) {
260 $roofshape =~ s/"//g;
261 if ($roofshape eq 'pyramidal') { $roofshape = 'pyramid'; }
262 printwows(" \"roofShape\": \"$roofshape\",\n");
264 if (defined($roofmaterial)) {
265 $roofshape =~ s/"//g;
266 printwows(" \"roofMaterial\": \"$roofmaterial\",\n");
268 if (defined($roofheight)) {
269 $roofheight = sprintf("%.1f", $roofheight);
270 printwows(" \"roofHeight\": \"$roofheight\",\n");
272 # this line is completely useless, but we need it to guarantee JSON has
273 # no trailing comma. osmbuildings will accept JSON with trailing commas
274 # just fine when you give it static, but NOT when requesting it dynamically.
275 printwows(" \"K9\": \"\"\n");