Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #!/usr/bin/perl
- package VectorTileServer;
- use warnings;
- use strict;
- use 5.012;
- # use TryCatch;
- use FileHandle;
- use Data::Dumper::Simple;
- # use Getopt::Long;
- use POSIX;
- # PostGIS
- # DBIx::Class
- use DBI;
- # use DBD::pg;
- # Server
- use Dancer;
- use Dancer::Logger::File;
- use Dancer::Logger::Console;
- # Protobuf
- use Google::ProtocolBuffers;
- # Config
- use TOML::Parser;
- # Math
- use Math::Trig;
- # Import constants pi2, pip2, pip4 (2*pi, pi/2, pi/4).
- use Math::Trig ':pi';
- # Import the conversions between cartesian/spherical/cylindrical.
- use Math::Trig ':radial';
- # Import the great circle formulas.
- use Math::Trig ':great_circle';
- # Database
- my $DBNAME = "geodev";
- my $DBUSER = "****";
- my $DBPASS = "****";
- # Get app config
- sub getServerConfig {
- my $CONFIG_FILE = shift;
- local $/ = undef;
- open(my $fh, '<', $CONFIG_FILE)
- or die "Could not open file '$CONFIG_FILE' $!";
- my $contents = <$fh>;
- close $fh;
- my $parser = TOML::Parser->new;
- my $config = $parser->parse($contents);
- return $config;
- }
- my $APP_CONFIG;
- my $CONFIG_FILE = "conf.toml";
- ######################################
- # Models
- # 1.0.1
- ######################################
- my $Default_Tile_Layer_Version = 2;
- my $Default_Tile_Layer_Extent = 4096;
- Google::ProtocolBuffers->parse("
- option optimize_for = LITE_RUNTIME;
- message Tile {
- // GeomType is described in section 4.3.4 of the specification
- enum GeomType {
- UNKNOWN = 0;
- POINT = 1;
- LINESTRING = 2;
- POLYGON = 3;
- }
- // Variant type encoding
- // The use of values is described in section 4.1 of the specification
- message Value {
- // Exactly one of these values must be present in a valid message
- optional string string_value = 1;
- optional float float_value = 2;
- optional double double_value = 3;
- optional int64 int_value = 4;
- optional uint64 uint_value = 5;
- optional sint64 sint_value = 6;
- optional bool bool_value = 7;
- extensions 8 to max;
- }
- // Features are described in section 4.2 of the specification
- message Feature {
- optional uint64 id = 1 [ default = 0 ];
- // Tags of this feature are encoded as repeated pairs of
- // integers.
- // A detailed description of tags is located in sections
- // 4.2 and 4.4 of the specification
- repeated uint32 tags = 2 [ packed = true ];
- // The type of geometry stored in this feature.
- optional GeomType type = 3 [ default = UNKNOWN ];
- // Contains a stream of commands and parameters (vertices).
- // A detailed description on geometry encoding is located in
- // section 4.3 of the specification.
- repeated uint32 geometry = 4 [ packed = true ];
- }
- // Layers are described in section 4.1 of the specification
- message Layer {
- // Any compliant implementation must first read the version
- // number encoded in this message and choose the correct
- // implementation for this version number before proceeding to
- // decode other parts of this message.
- required uint32 version = 15 [ default = 1 ];
- required string name = 1;
- // The actual features in this tile.
- repeated Feature features = 2;
- // Dictionary encoding for keys
- repeated string keys = 3;
- // Dictionary encoding for values
- repeated Value values = 4;
- // Although this is an 'optional' field it is required by the specification.
- // See https://github.com/mapbox/vector-tile-spec/issues/47
- optional uint32 extent = 5 [ default = 4096 ];
- extensions 16 to max;
- }
- repeated Layer layers = 3;
- extensions 16 to 8191;
- }",
- {create_accessors => 1 }
- );
- # https://github.com/mapbox/vector-tile-spec/tree/master/2.1
- # # Drawing the tile
- sub cmdEnc {
- my $id = shift;
- my $count = shift;
- return ($id & 0x7) | ($count << 3);
- }
- sub moveTo {
- my $count = shift;
- return cmdEnc(1, $count);
- }
- sub lineTo {
- my $count = shift;
- return cmdEnc(2, $count);
- }
- sub closePath {
- my $count = shift;
- return cmdEnc(7, $count);
- }
- sub paramEnc {
- my $value = shift;
- return (($value << 1) ^ ($value >> 31));
- }
- sub lngLatToTileXY {
- my $lng = shift;
- my $lat = shift;
- my $x = shift;
- my $y = shift;
- my $z = shift;
- my $totalTilesX = pow(2, $z);
- my $totalTilesY = pow(2, $z);
- my $lambda = ($lng + 180) / 180 * pi;
- # phi: [-pi/2, pi/2]
- my $phi = $lat / 180 * pi;
- my $tileX = $lambda / (2 * pi) * $totalTilesX;
- # [-1.4844, 1.4844] -> [1, 0] * totalTilesY
- my $tileY = (log(tan(pi/4-$phi/2))/pi/2 + 0.5) * $totalTilesY;
- return (($tileX - $x), ($tileY - $y));
- }
- ######################################
- # Lng Lat & TileXYZ Conversions
- # http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Perl
- ######################################
- sub getTileNumber {
- my ($lat,$lon,$zoom) = @_;
- my $xtile = int( ($lon+180)/360 * 2**$zoom ) ;
- my $ytile = int( (1 - log(tan(deg2rad($lat)) + sec(deg2rad($lat)))/pi)/2 * 2**$zoom ) ;
- return ($xtile, $ytile);
- }
- sub getLonLatFromTileXYZ {
- my ($xtile, $ytile, $zoom) = @_;
- my $n = 2 ** $zoom;
- my $lon_deg = $xtile / $n * 360.0 - 180.0;
- my $lat_deg = rad2deg(atan(sinh(pi * (1 - 2 * $ytile / $n))));
- return ($lon_deg, $lat_deg);
- }
- #
- sub Project {
- my ($X,$Y, $Zoom) = @_;
- my $Unit = 1 / (2 ** $Zoom);
- my $relY1 = $Y * $Unit;
- my $relY2 = $relY1 + $Unit;
- # note: $LimitY = ProjectF(degrees(atan(sinh(pi)))) = log(sinh(pi)+cosh(pi)) = pi
- # note: degrees(atan(sinh(pi))) = 85.051128..
- #my $LimitY = ProjectF(85.0511);
- # so stay simple and more accurate
- my $LimitY = pi;
- my $RangeY = 2 * $LimitY;
- $relY1 = $LimitY - $RangeY * $relY1;
- $relY2 = $LimitY - $RangeY * $relY2;
- my $Lat1 = ProjectMercToLat($relY1);
- my $Lat2 = ProjectMercToLat($relY2);
- $Unit = 360 / (2 ** $Zoom);
- my $Long1 = -180 + $X * $Unit;
- return ($Lat2, $Long1, $Lat1, $Long1 + $Unit); # S,W,N,E
- }
- sub ProjectMercToLat($){
- my $MercY = shift;
- return rad2deg(atan(sinh($MercY)));
- }
- sub ProjectF {
- my $Lat = shift;
- $Lat = deg2rad($Lat);
- my $Y = log(tan($Lat) + sec($Lat));
- return $Y;
- }
- # sub GeoJsonToProtoBuf {}
- sub FetchFeaturesForTile {
- my $ds = shift;
- my $x = shift;
- my $y = shift;
- my $z = shift;
- my @features = ();
- my ($S,$W,$N,$E) = Project($x, $y, $z);
- # info("$S,$W,$N,$En");
- # Connect to the database
- my $dbh = DBI->connect("DBI:Pg:dbname=$DBNAME;host=localhost;port=5432", "$DBUSER", "$DBPASS")
- or die "Couldn't open database: $DBI::errstr; stopped";
- # Prepare the SQL query for execution
- # ST_MakeEnvelope(left, bottom, right, top, srid)
- my $sth = $dbh->prepare("SELECT longitude, latitude FROM $ds WHERE geom && ST_MakeEnvelope($W, $S, $E, $N, 4326);")
- or die "Couldn't prepare statement: $DBI::errstr; stopped";
- # Execute the query
- $sth->execute()
- or die "Couldn't execute statement: $DBI::errstr; stopped";
- # Fetch each row and print it
- while ( my $row = $sth->fetchrow_hashref ) {
- # debug("longitude: $row->{longitude} t latitude: $row->{latitude}");latitude
- my $longitude = $row->{longitude};
- my $latitude = $row->{latitude};
- push(@features, {
- longitude => 0+$longitude,
- latitude => 0+$latitude
- });
- }
- $sth->finish();
- # Disconnect from the database
- $dbh->disconnect();
- return @features;
- }
- sub writeTileToFile {
- my $tile = shift;
- open my($fh), ">tile_debug.dat";
- binmode $fh;
- print $fh $tile;
- close $fh;
- }
- ######################################
- # Http Router
- ######################################
- # https://github.com/mapbox/vector-tile-spec
- any ['get', 'post'] => '/tiles/:datasource/:z/:x/:y' => sub {
- my $ds = params->{datasource};
- my $x = 0 + params->{x};
- my $y = 0 + params->{y};
- my $z = 0 + params->{z};
- push_header "Content-Type" => "application/x-protobuf";
- push_header "Access-Control-Allow-Origin" => "*";
- push_header "Access-Control-Allow-Methods" => "GET, POST, OPTIONS";
- my $features = &FetchFeaturesForTile($ds, $x, $y, $z);
- my @feats;
- my $id = 0;
- my $extent = $Default_Tile_Layer_Extent;
- foreach my $feat (@{$features}) {
- my @geom;
- my $cmd = moveTo(1);
- push(@geom, $cmd);
- my $pX = 0;
- my $pY = 0;
- my $longitude = $feat->{longitude};
- my $latitude = $feat->{latitude};
- my ($tile_x, $tile_y) = lngLatToTileXY($longitude, $latitude, $x, $y, $z);
- # Check if feature inside tile bounds
- if ($tile_x >= 0 && $tile_x < 1 && $tile_y >= 0 && $tile_y < 1) {
- my $deltaX = floor( ($extent * $tile_x + 0.5) ) - $pX;
- my $deltaY = floor( ($extent * $tile_y + 0.5) ) - $pY;
- push(@geom, paramEnc($deltaX));
- push(@geom, paramEnc($deltaY));
- push(@feats, {
- id => $id,
- type => 1,
- geometry => @geom
- });
- $id++;
- }
- }
- my $tileProtoBuf = Tile->encode({
- layers => [
- {
- version => 2,
- # name => $ds,
- name => "points",
- extent => $extent,
- features => @feats
- }
- ]
- });
- return $tileProtoBuf;
- };
- sub Init {
- $APP_CONFIG = getServerConfig($CONFIG_FILE);
- print(Dumper($APP_CONFIG->{database}));
- }
- sub Main {
- Init();
- dance();
- }
- Main();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement