#!/usr/bin/perl use Carp; use Getopt::Long; use LWP::Simple; use Math::Trig qw(great_circle_distance great_circle_destination great_circle_bearing deg2rad rad2deg); use POSIX; use File::Slurp; use File::Path qw(make_path remove_tree); sub getnode { my ($nodeid)=@_; my $lat; my $lon; if (defined $file) { print "Reading node $nodeid from file $file\n" if $verbose; ($nodeid,$lat,$lon)=getnodefromfile($nodeid); return ($nodeid,$lat,$lon); } else { print "Reading node $nodeid from api\n" if $verbose; ($nodeid,$lat,$lon)=getnodefromapi($nodeid); return ($nodeid,$lat,$lon); }; }; sub getnodefromapi { my ($nodeid)=@_; my $nodexml=get("http://api.openstreetmap.org/api/0.6/node/$nodeid"); my ($lat,$lon)=$nodexml=~/lat=[\'\"]([0-9\-\.]+)[\'\"] lon=[\'\"]([0-9\-\.]+)[\'\"]/gis; return ($nodeid,$lat,$lon); }; sub getnodefromfile { my ($nodeid)=@_; my $regex="()"; my $xml=read_file($file); ($nodexml)=$xml=~m#$regex#gis; my ($lat,$lon)=$nodexml=~/lat=[\'\"]([0-9\-\.]+)[\'\"] lon=[\'\"]([0-9\-\.]+)[\'\"]/gis; return ($nodeid,$lat,$lon); }; sub getway { my ($wayid)=@_; my $wayxml; if (defined $file) { print "Reading way $wayid from file $file\n" if $verbose; $wayxml=getwayfromfile($wayid); return ($wayxml); } else { print "Reading way $wayid from api\n" if $verbose; $wayxml=getwayfromapi($wayid); return ($wayxml); }; }; sub getwayfromapi { my ($wayid)=@_; my $wayxml=get("http://api.openstreetmap.org/api/0.6/way/$wayid"); return $wayxml; }; sub getwayfromfile { my ($wayid)=@_; my $regex="()"; my $xml=read_file($file); ($wayxml)=$xml=~m#$regex#gis; return $wayxml; }; sub rad2m { my ($rad)=@_; return $rad*6377997; }; sub m2rad { my ($m)=@_; return $m/6377997; }; sub bucket_span { my ($lat) = (@_); if ($lat>= 89.0 ) { return 360.0; } elsif ($lat>= 88.0 ) { return 8.0; } elsif ($lat>= 86.0 ) { return 4.0; } elsif ($lat>= 83.0 ) { return 2.0; } elsif ($lat>= 76.0 ) { return 1.0; } elsif ($lat>= 62.0 ) { return 0.5; } elsif ($lat>= 22.0 ) { return 0.25; } elsif ($lat>= -22.0 ) { return 0.125; } elsif ($lat>= -62.0 ) { return 0.25; } elsif ($lat>= -76.0 ) { return 0.5; } elsif ($lat>= -83.0 ) { return 1.0; } elsif ($lat>= -86.0 ) { return 2.0; } elsif ($lat>= -88.0 ) { return 4.0; } elsif ($lat>= -89.0 ) { return 8.0; } else { return 360.0; } } sub tile_index { my $EPSILON = 0.0000001; my ($lon, $lat) = (@_); my $lon_floor = POSIX::floor($lon); my $lat_floor = POSIX::floor($lat); my $span = bucket_span($lat); my $x; if ($span < $EPSILON) { $lon = 0; $x = 0; } elsif ($span <= 1.0) { $x = int(($lon - $lon_floor) / $span); } else { if ($lon >= 0) { $lon = int(int($lon/$span) * $span); } else { $lon = int(int(($lon+1)/$span) * $span - $span); if ($lon < -180) { $lon = -180; } } $x = 0; } my $y; $y = int(($lat - $lat_floor) * 8); my $index = 0; $index += ($lon_floor + 180) << 14; $index += ($lat_floor + 90) << 6; $index += $y << 3; $index += $x; return $index; } my @way; # Constants my $pi=3.14159265358979323846; my $closedway=0; # Defaults my $wayid = 29827954; my $nodecount = 0; my $height = 20; my $modelname = "Building"; $result = GetOptions ("clean" => \$clean, "f|file=s" => \$file, "h|height=f" => \$height, "help|?" => \$help, "lat=f" => \$latitude, "lon=f" => \$longitude, "n|name=s" => \$modelname, "v|verbose" => \$verbose, "w|way=i" => \$wayid, "x|xmlwrapper" => \$wrapper ); if ($help) { print "\n\nUsage: bob [ options ... ]\n\n"; print "General options:\n"; print " -help, -? Show command line options\n"; print " -verbose, -v Show intermediate console output\n\n"; print "Building options:\n"; print " -clean Clean nodes before building (removes adjacent\n"; print " nodes that lie on a straight line. This greatly\n"; print " improves performance and the number of houses with\n"; print " a gable roof.)\n"; print " -file, -f Path to an input OSM file\n"; print " -height, -h Default building height in meters\n"; print " -name, -n Name of the model\n"; print " -lat Latitude in degrees of a building location\n"; print " -lon Longitude in degrees of a building location\n"; print " -xmlwrapper, -x Create a basic XML file for each model\n"; print " -way, -w Way ID as found in OSM database\n\n"; print "Examples:\n"; print " bob -w 29827954 Generate a single building\n"; print " bob -f map.osm -h 10 -c Generate all buildings from file, with a default\n"; print " height of 10m, cleaning redundant nodes.\n\n"; exit; } my $wayxml=getway($wayid); # Get osm building data from way sub getosmdata { my ($wayheight) = $wayxml=~/tag k=\"height\" v=[\'\"]([0-9]+)[\'\"]/gis; my ($waylevels) = $wayxml=~/tag k=\"building:levels\" v=[\'\"]([0-9]+)[\'\"]/gis; my ($wayroofshape) = $wayxml=~/tag k=\"building:roof:shape\" v=[\'\"]([0-9]+)[\'\"]/gis; # No height given, number of floors maybe? if ($wayheight == "") { # One floor is roughly 3m $wayheight = $waylevels * 3; }; # Use given/estimated height if ($wayheight != "") { $height = $wayheight; } # Fall back to default height, with randomly an extra level else { $height = $height + int(rand(2))*3; }; if ($wayroofshape != "") { $roofshape = $wayroofshape; } # Estimate roof shape elsif ($height < 25) { $roofshape = "2.0"; }; print "getosmdata: returning height=$height, levels=$waylevels, roof=$roofshape\n" if ($verbose); return ($height,$waylevels,$roofshape); }; my $wayxml=getway($wayid); # Assemble way data structure foreach ($wayxml=~/nd ref=[\'\"]([0-9]+)[\'\"]/gis) { ($nodeid,$lat,$lon)=&getnode($_); $way[$nodecount]->{'node'} = $nodeid; $way[$nodecount]->{'lat'} = $lat; $way[$nodecount]->{'lon'} = $lon; $way[$nodecount]->{'x'} = 0; $way[$nodecount]->{'y'} = 0; print $nodecount." ".$nodeid." ".$lat." ".$lon."\n" if $verbose; $nodecount++; }; my $reflat; my $reflon; if (defined($latitude)) { $reflat=$latitude; } else { $reflat=$way[0]->{'lat'}; }; if (defined($longitude)) { $reflon=$longitude; } else { $reflon=$way[0]->{'lon'}; }; # Process data structure $max=@way-1; if ($way[0]->{'node'}!=$way[$max]->{'node'}) { # Work around!! Consider all ways "closed", even if they are not!! $closedway=1; # $closedway=0; # carp "This is not a closed way"; # exit 1; } else { $closedway=1; }; print "Positioning nodes:\n" if $verbose; for ($node=0 ; $node<=$max ; $node++) { if ($way[$node]->{'lon'}==$reflon && $way[$node]->{'lat'}==$reflat) { $way[$node]->{'x'} = 0; $way[$node]->{'y'} = 0; } else { $distancerad = great_circle_distance(deg2rad($reflon),deg2rad(90-$reflat) ,deg2rad($way[$node]->{'lon'}),deg2rad(90-$way[$node]->{'lat'})); $bearingrad = great_circle_bearing(deg2rad($reflon),deg2rad(90-$reflat) ,deg2rad($way[$node]->{'lon'}),deg2rad(90-$way[$node]->{'lat'})); $distancem=&rad2m($distancerad); $way[$node]->{'x'}=$distancem*cos($bearingrad); $way[$node]->{'y'}=$distancem*sin($bearingrad); }; print $node." ".$way[$node]->{'x'}." ".$way[$node]->{'y'}."\n" if $verbose; }; # Remove useless nodes if ($clean) { sub cleaner { $maxi = $max-1; for ($n=@_; $n<=$maxi; $n++) { $node1 = $n+1; if ($node1 > $maxi) { $node1 = $node1-$maxi-1; }; $node2 = $n+2; if ($node2 > $maxi) { $node2 = $node2-$maxi-1; }; $x0 = $way[$n]->{'x'}; $x1 = $way[$node1]->{'x'}; $x2 = $way[$node2]->{'x'}; $y0 = $way[$n]->{'y'}; $y1 = $way[$node1]->{'y'}; $y2 = $way[$node2]->{'y'}; # Calculate distance from node to line between two adjacent nodes $labda = (($x2-$x0)*($x1-$x0)+($y2-$y0)*($y1-$y0))/(($x2-$x0)**2+($y2-$y0)**2); $dist = sqrt(($x1 - $x0 - $labda * ($x2-$x0))**2+($y1-$y0-$labda*($y2-$y0))**2); # Remove node if it is within 50 cm from the line if ($dist < 0.5){ splice(@way,($node1),1); $maxi=$maxi-1; $max=$max-1; # Restart cleaner at the previous node, because the second node is different now. cleaner($n); }; }; }; cleaner(0); }; # Create ready-to-use output directory structure my $path; if ($reflon >= 0) { $path.="e".sprintf("%03d",floor($reflon/10)*10); } else { $path.="w".sprintf("%03d",ceil(($reflon*-1)/10)*10); } if ($reflat >= 0) { $path.="n".sprintf("%02d",floor($reflat/10)*10); } else { $path.="s".sprintf("%02d",ceil($reflat*-1/10)*10); } $path .= "/"; if ($reflon >= 0) { $path.="e".sprintf("%03d",floor($reflon)); } else { $path.="w".sprintf("%03d",ceil($reflon*-1)); } if ($reflat >= 0) { $path.="n".sprintf("%02d",floor($reflat)); } else { $path.="s".sprintf("%02d",ceil($reflat*-1)); } # Create the folder $path = "Output/Objects/".$path; make_path($path); $filebase = $path."/osm".$wayid; print "Output to $filebase.ac\n" if ($verbose); open(AC,">$filebase.ac"); # Get osm building data my ($height,$levels,$roofshape) = getosmdata(); # Number of vertices my $totalverts; my $layerverts; if ($closedway==1) { $totalverts=(@way-1)*2; $layerverts=@way-1; } else { $totalverts=(@way)*2; $layerverts=(@way); }; # Disable gable roof for models with more than 4 corners. if (($roofshape == 2.0) and ($totalverts!=8)) { $roofshape = 0; } # Textures if ($roofshape == 2.0) { $walltexture = "wall.png"; } else { $walltexture = "flat".int(rand(3)).".png"; } $rooftexture = "roof.png"; print AC "AC3Db MATERIAL \"DefaultWhite\" rgb 1 1 1 amb 1 1 1 emis 0 0 0 spec 0.5 0.5 0.5 shi 64 trans 0 MATERIAL \"Roof\" rgb 1 1 1 amb 1 1 1 emis 0 0 0 spec 0.5 0.5 0.5 shi 64 trans 0 OBJECT world kids 1 OBJECT group name \"".$modelname."\"\n"; if ($roofshape == "2") { print AC "kids 2\n" } else { print AC "kids 1\n" }; print AC "OBJECT poly\" name \"Walls\" texture \"".$walltexture."\" texrep 1 1\n"; if ($roofshape == "0") { print AC "numvert ".$totalverts."\n"; } else { print AC "numvert ".($totalverts+2)."\n"; }; for ($node=0 ; $node<$layerverts ; $node++) { print AC $way[$node]->{'x'}." $height ".$way[$node]->{'y'}."\n"; }; for ($node=0 ; $node<$layerverts ; $node++) { print AC $way[$node]->{'x'}." -5 ".$way[$node]->{'y'}."\n"; }; if ($roofshape == "2.0") { # Calculate length of two sides, to propose a roof orientation $side[0] = sqrt((abs($way[1]->{'x'}-$way[0]->{'x'})) ** 2+(abs($way[1]->{'y'}-$way[0]->{'y'})) ** 2); $side[1] = sqrt((abs($way[1]->{'x'}-$way[2]->{'x'})) ** 2+(abs($way[1]->{'y'}-$way[2]->{'y'})) ** 2); # Two vertices for the roof, location depends on roof orientation $roofheight = (2/3)*$side[$side[0] > $side[1]]; if ($side[0] < $side[1]){ $roofvert[0] = (($way[0]->{'x'}+$way[1]->{'x'})/2)." ".($height+$roofheight)." ".(($way[0]->{'y'}+$way[1]->{'y'})/2)."\n"; $roofvert[1] = (($way[2]->{'x'}+$way[3]->{'x'})/2)." ".($height+$roofheight)." ".(($way[2]->{'y'}+$way[3]->{'y'})/2)."\n"; } else { $roofvert[0] = (($way[1]->{'x'}+$way[2]->{'x'})/2)." ".($height+$roofheight)." ".(($way[1]->{'y'}+$way[2]->{'y'})/2)."\n"; $roofvert[1] = (($way[3]->{'x'}+$way[0]->{'x'})/2)." ".($height+$roofheight)." ".(($way[3]->{'y'}+$way[0]->{'y'})/2)."\n"; } print AC $roofvert[0]; print AC $roofvert[1]; $surfaces=$layerverts+2; print AC "numsurf ".$surfaces."\n"; } else { # Generate roof poly if ($closedway==1) { $surfaces=$layerverts+1; print AC "numsurf ".$surfaces."\n"; print AC "SURF 0x20\n"; print AC "mat 1\n"; print AC "refs ".($layerverts-1)."\n"; for ($node=$max-1 ; $node>0 ; $node--) { print AC $node ." 0 0\n"; }; } else { $surfaces=$layerverts-1; print AC "numsurf ".$surfaces."\n"; }; }; # Generate walls $sidenum = 0; for ($wall=0 ; $wall<$layerverts ; $wall++) { print AC "SURF 0x20\n"; print AC "mat 0\n"; print AC "refs 4\n"; # Special case for final wall if (($wall==($layerverts-1)) and ($closedway==1)) { $tl=0; $tr=$max-1; $bl=$max; $br=(2*$max)-1; } else { $tr=$wall; $tl=$wall+1; $br=$wall+$layerverts; $bl=$wall+$layerverts+1; }; # calculate texture scales (how often should the texture repeat?) # one texture is 3m high and wide $texturex = 6; $texturey = 6; if ($levels == "") { if ($roofshape == 2.0) { $texturescaley = ($height+5) / $texturey; } else { $texturescaley = ($height+5) / 3 / 2 / 10; }; } else { if ($roofshape == 2.0) { $texturescaley = $levels / 2 / 2; } else { $texturescaley = $levels / 2 / 10; }; }; $texturescalex = $side[$sidenum] / 2 / $texturex; if ($sidenum == 1) { $sidenum = 0; } else { $sidenum++; }; print AC $tr." -".$texturescalex." 0\n"; print AC $tl." ".$texturescalex." 0\n"; print AC $bl." ".$texturescalex." -".$texturescaley."\n"; print AC $br." -".$texturescalex." -".$texturescaley."\n"; }; # Vertical end faces of the triangle roof if ($roofshape=="2.0") { $texturescaley = $roofheight / $texturey; $texturescalex = $side[($side[0] < $side[1])] / 2 / $texturex; for ($node=0 ; $node<2 ; $node++) { $bl=1+(2*$node)+($side[0] > $side[1]); $br=2*$node+($side[0] > $side[1]); $tc=($totalverts)+$node; if ($bl > 3) { $bl = 0; }; if ($br > 3) { $br = 0; }; print AC "SURF 0x20\n"; print AC "mat 0\n"; print AC "refs 3\n"; print AC $bl." ".$texturescalex." 0\n"; print AC $br." ".$texturescalex." 0\n"; print AC $tc." 0 ".$texturescaley."\n"; }; print AC "kids 0\n"; print AC "OBJECT poly name \"Roof\" texture \"roof.png\" texrep 1 1\n"; print AC "numvert 6\n"; for ($node=0 ; $node<$layerverts ; $node++) { print AC $way[$node]->{'x'}." $height ".$way[$node]->{'y'}."\n"; }; # Two vertices for the roof print AC $roofvert[0]; print AC $roofvert[1]; print AC "numsurf 2\n"; $texturescaley = $roofheight / $texturey; $texturescalex = $side[($side[0] < $side[1])] / 4 / $texturex; # Actual roof, exists of two faces for ($node=0 ; $node<2 ; $node++) { $bl=(2*$node)+($side[0] > $side[1]); $br=$bl-1; $tl=5-$node; $tr=5-1+$node; if ($bl > 3) { $bl = $bl-3; }; if ($br > 3) { $br = $br-3; }; if ($bl < 0) { $bl = $bl+4; }; if ($br < 0) { $br = $br+4; }; print AC "SURF 0x20\n"; print AC "mat 1\n"; print AC "refs 4\n"; print AC $bl." ".$texturescalex." 0\n"; print AC $br." -".$texturescalex." 0\n"; print AC $tl." -".$texturescalex." 1\n"; print AC $tr." ".$texturescalex." 1\n"; }; }; print AC "kids 0\n"; close(AC); # Write xml wrapper if ($wrapper) { open(XML,">$filebase.xml"); print XML "\n"; print XML "\n"; print XML " osm".$wayid.".ac\n"; print XML " \n"; print XML " range\n"; print XML " 0\n"; print XML " 15000\n"; print XML " \n"; print XML " \n"; print XML " select\n"; print XML " \n"; print XML " /scenery/osm\n"; print XML " \n"; print XML " \n"; print XML " \n"; print XML " Effects/lightmap\n"; print XML " \n"; print XML " \n"; print XML " flat-lit.png\n"; print XML " \n"; print XML " \n"; print XML " ".$modelname."\n"; print XML " \n"; print XML "\n"; close (XML); }; # Write stg $tile=tile_index($reflon,$reflat); $stgfile = $path."/".$tile.".stg"; open(STG,">>$stgfile"); if ($wrapper) { print STG "OBJECT_STATIC osm".$wayid.".xml ".$reflon." ".$reflat." -9999 180\n"; } else { print STG "OBJECT_STATIC osm".$wayid.".ac ".$reflon." ".$reflat." -9999 180\n"; }; close(STG); exit 0;