3 # ----------------------
4 # Tiny AMF read-only API
5 # Richard Fairhurst 2010
8 # This is the simplest possible server for Halcyon (Flash vector map
9 # renderer) to read from an OpenStreetMap database - populated by
10 # Osmosis, for example. It has no dependencies other than DBI. It
11 # expects to run on Apache or another server that populates the
12 # CONTENT_LENGTH environment variable.
14 # The database should have the current_ tables populated, and be
15 # consistent with a changeset and user table containing at least one
16 # entry each. Edit the DBI->connect line to contain the connection
17 # details for your database.
19 # Configure Halcyon's connection like this:
20 # fo.addVariable("api","tinyamf.cgi?");
21 # fo.addVariable("connection","AMF");
23 # Note the question mark at the end of tinyamf.cgi.
25 # Questions? Patches? Please subscribe to the potlatch-dev mailing
26 # list at lists.openstreetmap.org and ask there.
28 # With thanks to Musicman (AMF) and Tom Hughes (quadtiles) from whose
29 # PHP and Ruby code some of this is adapted.
31 # The following globals are maintained throughout the program:
33 # $offset - position in input file
34 # $result - response file
35 # $results - number of responses
36 # $dbh - database handle
37 # $ppc - PowerPC or Intel byte-order
40 $dbh=DBI->connect('DBI:mysql:openstreetmap','openstreetmap','openstreetmap', { RaiseError =>1 } );
45 $l=$ENV{'CONTENT_LENGTH'};
48 $tmp=pack("d", 1); $ppc=0;
49 if ($tmp eq "\0\0\0\0\0\0\360\77") { $ppc=0; }
50 elsif ($tmp eq "\77\360\0\0\0\0\0\0") { $ppc=1; }
51 else { die "Unknown byte order\n"; }
57 $hc=ord(substr($d,$offset++,1));
59 $key=getstr($d, $offset);
61 $lo=getlength($d, $offset); # not used
62 $ch=ord(substr($d,$offset++,1));
63 $val=parseitem($ch, $offset);
69 $result=''; $results=0;
74 $fn=getstr($d, $offset);
76 # - Get number in sequence
77 $seq=substr(getstr($d, $offset),1);
78 $lo=getlength($d, $offset); # length of all params? not used
80 # - Get all parameters (sent as an array, hence the '10')
82 $ch=ord(substr($d,$offset++,1)); if ($ch!=10) { print "Error - expecting array"; }
83 $lo=getlength($d, $offset);
84 for ($ni=0; $ni<$lo; $ni++) {
85 $ch=ord(substr($d,$offset++,1));
86 $p=parseitem($ch, $offset);
90 if ($fn eq 'whichways') { addresult($seq,whichways(@params)); }
91 elsif ($fn eq 'getway') { addresult($seq,getway(@params)); }
92 elsif ($fn eq 'getrelation') { addresult($seq,getrelation(@params)); }
96 # ----- Write response
100 print "Content-type: application/x-amf\n\n";
102 print pack("n",$results);
106 # ====================================================================================
110 my ($query,$query2,$sql,$id,$lat,$lon,$v,$k,$vv);
111 my ($xmin,$ymin,$xmax,$ymax)=@_;
112 my $enlarge = ($xmax-$xmin)/8; if ($enlarge<0.01) { $enlarge=0.01; }
113 $xmin -= $enlarge; $ymin -= $enlarge;
114 $xmax += $enlarge; $ymax += $enlarge;
115 my $sqlarea=sql_for_area($ymin,$xmin,$ymax,$xmax,'current_nodes.');
120 SELECT DISTINCT current_ways.id AS wayid,current_ways.version AS version
121 FROM current_way_nodes
122 INNER JOIN current_nodes ON current_nodes.id=current_way_nodes.node_id
123 INNER JOIN current_ways ON current_ways.id =current_way_nodes.id
124 WHERE current_nodes.visible=TRUE
125 AND current_ways.visible=TRUE
128 $query=$dbh->prepare($sql); $query->execute();
129 my $ways=(); my @wayids=();
130 while (($id,$v)=$query->fetchrow_array()) { push (@ways,[$id,$v]); push (@wayids,$id); }
136 SELECT current_nodes.id,current_nodes.latitude*0.0000001 AS lat,current_nodes.longitude*0.0000001 AS lon,current_nodes.version
138 LEFT OUTER JOIN current_way_nodes cwn ON cwn.node_id=current_nodes.id
139 WHERE current_nodes.visible=TRUE
143 $query=$dbh->prepare($sql); $query->execute();
145 while (($id,$lat,$lon,$v)=$query->fetchrow_array()) {
147 $query2=$dbh->prepare("SELECT k,v FROM current_node_tags WHERE id=?");
148 $query2->execute($id); while (($k,$vv)=$query2->fetchrow_array()) { $tags{$k}=$vv; }
150 push (@pois,[$id,$lon,$lat,{%tags},$v]);
154 # - Relations in area
157 SELECT DISTINCT cr.id AS relid,cr.version AS version
158 FROM current_relations cr
159 INNER JOIN current_relation_members crm ON crm.id=cr.id
160 INNER JOIN current_nodes ON crm.member_id=current_nodes.id AND crm.member_type='Node'
166 SELECT DISTINCT cr.id AS relid,cr.version AS version
167 FROM current_relations cr
168 INNER JOIN current_relation_members crm ON crm.id=cr.id
169 WHERE crm.member_type='Way'
170 AND crm.member_id IN (@wayids)
173 $query=$dbh->prepare($sql); $query->execute();
175 while (($id,$v)=$query->fetchrow_array()) { push (@rels,[$id,$v]); }
178 return [0,'',[@ways],[@pois],[@rels]];
181 # ====================================================================================
186 my ($sql,$query,$lat,$lon,$id,$v,$k,$vv,$uid,%tags);
188 SELECT latitude*0.0000001 AS lat,longitude*0.0000001 AS lon,current_nodes.id,current_nodes.version
189 FROM current_way_nodes,current_nodes
190 WHERE current_way_nodes.id=?
191 AND current_way_nodes.node_id=current_nodes.id
192 AND current_nodes.visible=TRUE
195 $query=$dbh->prepare($sql); $query->execute($wayid);
197 while (($lat,$lon,$id,$v)=$query->fetchrow_array()) {
199 $query2=$dbh->prepare("SELECT k,v FROM current_node_tags WHERE id=?");
200 $query2->execute($id); while (($k,$vv)=$query2->fetchrow_array()) { $tags{$k}=$vv; }
202 push (@points,[$lon,$lat,$id,{%tags},$v]);
206 $query=$dbh->prepare("SELECT k,v FROM current_way_tags WHERE id=?"); $query->execute($wayid);
208 while (($k,$vv)=$query->fetchrow_array()) { $tags{$k}=$vv; }
211 $query=$dbh->prepare("SELECT version FROM current_ways WHERE id=?"); $query->execute($wayid);
212 $v=$query->fetchrow_array();
215 $query=$dbh->prepare("SELECT user_id FROM current_ways,changesets WHERE current_ways.id=? AND current_ways.changeset_id=changesets.id"); $query->execute($wayid);
216 $uid=$query->fetchrow_array();
219 return [0, '', $wayid, [@points], {%tags}, $v, $uid];
222 # ====================================================================================
227 my ($sql,$query,$v,$k,$vv,$type,$id,$role);
229 $query=$dbh->prepare("SELECT member_type,member_id,member_role FROM current_relation_members,current_relations WHERE current_relations.id=? AND current_relation_members.id=current_relations.id ORDER BY sequence_id");
230 $query->execute($relid);
232 while (($type,$id,$role)=$query->fetchrow_array()) { push(@members,[ucfirst $type,$id,$role]); }
235 $query=$dbh->prepare("SELECT k,v FROM current_relation_tags WHERE id=?"); $query->execute($relid);
237 while (($k,$vv)=$query->fetchrow_array()) { $tags{$k}=$vv; }
240 $query=$dbh->prepare("SELECT version FROM current_relations WHERE id=?"); $query->execute($relid);
241 $v=$query->fetchrow_array();
244 return [0, '', $relid, {%tags}, [@members], $v];
248 # ====================================================================================
249 # AMF decoding routines
251 # returns object of unknown type
255 if ($ch==0) { return getnumber(); } # number
256 elsif ($ch==1) { return ord(subtr($d,$offset++,1)); } # boolean
257 elsif ($ch==2) { return getstr(); } # string
258 elsif ($ch==3) { return getobj(); } # object
259 elsif ($ch==5) { return undef; } # null
260 elsif ($ch==6) { return undef; } # undefined
261 elsif ($ch==8) { return getmixed(); } # mixedArray
262 elsif ($ch==10){ return getarray(); } # array
264 print "Didn't recognise type $ch\n";
268 my $hi=ord(substr($d,$offset++,1));
269 my $lo=ord(substr($d,$offset++,1))+256*$hi;
270 my $val=substr($d,$offset,$lo);
278 if ($ppc) { $ibf=substr($d,$offset,8); }
279 else { for (my $nc=7; $nc>=0; $nc--) { $ibf.=substr($d,$offset+$nc,1); } }
281 return unpack("d", $ibf);
287 while($key=getstr()) {
288 $ch=ord(substr($d,$offset++,1));
289 $ret{$key}=parseitem($ch);
291 $ch=ord(substr($d,$offset++,1));
292 if ($ch!=9) { print "Unexpected object end: $ch"; }
304 for (my $ni=0; $ni<$lo; $ni++) {
305 my $ch=ord(substr($d,$offset++,1));
306 push (@ret,parseitem($ch));
312 # ====================================================================================
313 # AMF encoding routines
315 # $data is object of unknown type
317 my $seq=$_[0]; my $data=$_[1];
319 $result.=sendstr("/$seq/onResult").sendstr("null").pack("N",-1).sendobj($data);
322 # $ref is a reference to an object of unknown type
328 if ($type eq 'ARRAY') {
329 # Send as array (code 10)
331 my $ret="\12".pack("N",$#arr+1);
332 for ($n=0; $n<=$#arr; $n++) { $ret.=sendobj($arr[$n]); }
335 } elsif ($type eq 'HASH') {
336 # Send as object (code 3)
339 foreach $key (keys %hash) { $ret.=sendstr($key).sendobj($hash{$key}); }
340 return $ret.sendstr('')."\11";
342 } elsif ($ref=~/^[+\-]?[\d\.]+$/) {
343 # Send as number (code 0)
344 return "\0" . sendnum($ref);
347 # Send as string (code 2)
348 return "\2" . sendstr($ref);
359 return pack("n", length($b)).$b;
363 my $b=pack("d", $_[0]);
364 if ($ppc) { return $b; }
365 my $r=''; for (my $n=7; $n>=0; $n--) { $r.=substr($b,$n,1); }
371 for (my $c=0; $c<4; $c++) {
373 $b+=ord(substr($d,$offset++,1));
378 # ================================================================
379 # OSM quadtile routines
380 # based on original Ruby code by Tom Hughes
383 my $lat=$_[0]; my $lon=$_[1];
384 return tile_for_xy(round(($lon+180)*65535/360),round(($lat+90)*65535/180));
388 return int($_[0] + .5 * ($_[0] <=> 0));
392 my $minlat=$_[0]; my $minlon=$_[1];
393 my $maxlat=$_[2]; my $maxlon=$_[3];
395 $minx=round(($minlon + 180) * 65535 / 360);
396 $maxx=round(($maxlon + 180) * 65535 / 360);
397 $miny=round(($minlat + 90 ) * 65535 / 180);
398 $maxy=round(($maxlat + 90 ) * 65535 / 180);
401 for ($x=$minx; $x<=$maxx; $x++) {
402 for ($y=$miny; $y<=$maxy; $y++) {
403 push(@tiles,tile_for_xy($x,$y));
415 for ($i=0; $i<16; $i++) {
417 unless (($x & 0x8000)==0) { $t=$t | 1; }
421 unless (($y & 0x8000)==0) { $t=$t | 1; }
428 my $minlat=$_[0]; my $minlon=$_[1];
429 my $maxlat=$_[2]; my $maxlon=$_[3];
431 my @tiles=tiles_for_area($minlat,$minlon,$maxlat,$maxlon);
440 foreach $tile (sort @tiles) {
441 if ($tile==$last+1) {
442 # part of a run, so keep going
447 if ($rl<3) { push (@singles,@run); }
448 else { $sql.="${prefix}tile BETWEEN ".$run[0].' AND '.$run[$rl-1]." OR "; }
455 if ($rl<3) { push (@singles,@run); }
456 else { $sql.="${prefix}tile BETWEEN ".$run[0].' AND '.$run[$rl-1]." OR "; }
457 if ($#singles>-1) { $sql.="${prefix}tile IN (".join(',',@singles).') '; }