Advertisement
Guest User

Untitled

a guest
Aug 28th, 2017
89
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 9.86 KB | None | 0 0
  1. #!/usr/bin/perl
  2. package VectorTileServer;
  3.  
  4. use warnings;
  5. use strict;
  6. use 5.012;
  7. # use TryCatch;
  8. use FileHandle;
  9. use Data::Dumper::Simple;
  10. # use Getopt::Long;
  11. use POSIX;
  12. # PostGIS
  13. # DBIx::Class
  14. use DBI;
  15. # use DBD::pg;
  16. # Server
  17. use Dancer;
  18. use Dancer::Logger::File;
  19. use Dancer::Logger::Console;
  20. # Protobuf
  21. use Google::ProtocolBuffers;
  22. # Config
  23. use TOML::Parser;
  24. # Math
  25. use Math::Trig;
  26.  
  27. # Import constants pi2, pip2, pip4 (2*pi, pi/2, pi/4).
  28. use Math::Trig ':pi';
  29. # Import the conversions between cartesian/spherical/cylindrical.
  30. use Math::Trig ':radial';
  31. # Import the great circle formulas.
  32. use Math::Trig ':great_circle';
  33.  
  34. # Database
  35. my $DBNAME = "geodev";
  36. my $DBUSER = "****";
  37. my $DBPASS = "****";
  38.  
  39. # Get app config
  40. sub getServerConfig {
  41. my $CONFIG_FILE = shift;
  42.  
  43. local $/ = undef;
  44. open(my $fh, '<', $CONFIG_FILE)
  45. or die "Could not open file '$CONFIG_FILE' $!";
  46. my $contents = <$fh>;
  47. close $fh;
  48.  
  49. my $parser = TOML::Parser->new;
  50. my $config = $parser->parse($contents);
  51. return $config;
  52. }
  53. my $APP_CONFIG;
  54. my $CONFIG_FILE = "conf.toml";
  55.  
  56.  
  57. ######################################
  58. # Models
  59. # 1.0.1
  60. ######################################
  61.  
  62. my $Default_Tile_Layer_Version = 2;
  63. my $Default_Tile_Layer_Extent = 4096;
  64.  
  65.  
  66. Google::ProtocolBuffers->parse("
  67. option optimize_for = LITE_RUNTIME;
  68.  
  69. message Tile {
  70.  
  71. // GeomType is described in section 4.3.4 of the specification
  72. enum GeomType {
  73. UNKNOWN = 0;
  74. POINT = 1;
  75. LINESTRING = 2;
  76. POLYGON = 3;
  77. }
  78.  
  79. // Variant type encoding
  80. // The use of values is described in section 4.1 of the specification
  81. message Value {
  82. // Exactly one of these values must be present in a valid message
  83. optional string string_value = 1;
  84. optional float float_value = 2;
  85. optional double double_value = 3;
  86. optional int64 int_value = 4;
  87. optional uint64 uint_value = 5;
  88. optional sint64 sint_value = 6;
  89. optional bool bool_value = 7;
  90.  
  91. extensions 8 to max;
  92. }
  93.  
  94. // Features are described in section 4.2 of the specification
  95. message Feature {
  96. optional uint64 id = 1 [ default = 0 ];
  97.  
  98. // Tags of this feature are encoded as repeated pairs of
  99. // integers.
  100. // A detailed description of tags is located in sections
  101. // 4.2 and 4.4 of the specification
  102. repeated uint32 tags = 2 [ packed = true ];
  103.  
  104. // The type of geometry stored in this feature.
  105. optional GeomType type = 3 [ default = UNKNOWN ];
  106.  
  107. // Contains a stream of commands and parameters (vertices).
  108. // A detailed description on geometry encoding is located in
  109. // section 4.3 of the specification.
  110. repeated uint32 geometry = 4 [ packed = true ];
  111. }
  112.  
  113. // Layers are described in section 4.1 of the specification
  114. message Layer {
  115. // Any compliant implementation must first read the version
  116. // number encoded in this message and choose the correct
  117. // implementation for this version number before proceeding to
  118. // decode other parts of this message.
  119. required uint32 version = 15 [ default = 1 ];
  120.  
  121. required string name = 1;
  122.  
  123. // The actual features in this tile.
  124. repeated Feature features = 2;
  125.  
  126. // Dictionary encoding for keys
  127. repeated string keys = 3;
  128.  
  129. // Dictionary encoding for values
  130. repeated Value values = 4;
  131.  
  132. // Although this is an 'optional' field it is required by the specification.
  133. // See https://github.com/mapbox/vector-tile-spec/issues/47
  134. optional uint32 extent = 5 [ default = 4096 ];
  135.  
  136. extensions 16 to max;
  137. }
  138.  
  139. repeated Layer layers = 3;
  140.  
  141. extensions 16 to 8191;
  142. }",
  143. {create_accessors => 1 }
  144. );
  145.  
  146.  
  147.  
  148. # https://github.com/mapbox/vector-tile-spec/tree/master/2.1
  149.  
  150. # # Drawing the tile
  151. sub cmdEnc {
  152. my $id = shift;
  153. my $count = shift;
  154. return ($id & 0x7) | ($count << 3);
  155. }
  156.  
  157. sub moveTo {
  158. my $count = shift;
  159. return cmdEnc(1, $count);
  160. }
  161.  
  162. sub lineTo {
  163. my $count = shift;
  164. return cmdEnc(2, $count);
  165. }
  166.  
  167. sub closePath {
  168. my $count = shift;
  169. return cmdEnc(7, $count);
  170. }
  171.  
  172. sub paramEnc {
  173. my $value = shift;
  174. return (($value << 1) ^ ($value >> 31));
  175. }
  176.  
  177. sub lngLatToTileXY {
  178. my $lng = shift;
  179. my $lat = shift;
  180. my $x = shift;
  181. my $y = shift;
  182. my $z = shift;
  183. my $totalTilesX = pow(2, $z);
  184. my $totalTilesY = pow(2, $z);
  185. my $lambda = ($lng + 180) / 180 * pi;
  186. # phi: [-pi/2, pi/2]
  187. my $phi = $lat / 180 * pi;
  188. my $tileX = $lambda / (2 * pi) * $totalTilesX;
  189. # [-1.4844, 1.4844] -> [1, 0] * totalTilesY
  190. my $tileY = (log(tan(pi/4-$phi/2))/pi/2 + 0.5) * $totalTilesY;
  191. return (($tileX - $x), ($tileY - $y));
  192. }
  193.  
  194. ######################################
  195. # Lng Lat & TileXYZ Conversions
  196. # http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames#Perl
  197. ######################################
  198. sub getTileNumber {
  199. my ($lat,$lon,$zoom) = @_;
  200. my $xtile = int( ($lon+180)/360 * 2**$zoom ) ;
  201. my $ytile = int( (1 - log(tan(deg2rad($lat)) + sec(deg2rad($lat)))/pi)/2 * 2**$zoom ) ;
  202. return ($xtile, $ytile);
  203. }
  204.  
  205. sub getLonLatFromTileXYZ {
  206. my ($xtile, $ytile, $zoom) = @_;
  207. my $n = 2 ** $zoom;
  208. my $lon_deg = $xtile / $n * 360.0 - 180.0;
  209. my $lat_deg = rad2deg(atan(sinh(pi * (1 - 2 * $ytile / $n))));
  210. return ($lon_deg, $lat_deg);
  211. }
  212.  
  213. #
  214. sub Project {
  215. my ($X,$Y, $Zoom) = @_;
  216. my $Unit = 1 / (2 ** $Zoom);
  217. my $relY1 = $Y * $Unit;
  218. my $relY2 = $relY1 + $Unit;
  219.  
  220. # note: $LimitY = ProjectF(degrees(atan(sinh(pi)))) = log(sinh(pi)+cosh(pi)) = pi
  221. # note: degrees(atan(sinh(pi))) = 85.051128..
  222. #my $LimitY = ProjectF(85.0511);
  223.  
  224. # so stay simple and more accurate
  225. my $LimitY = pi;
  226. my $RangeY = 2 * $LimitY;
  227. $relY1 = $LimitY - $RangeY * $relY1;
  228. $relY2 = $LimitY - $RangeY * $relY2;
  229. my $Lat1 = ProjectMercToLat($relY1);
  230. my $Lat2 = ProjectMercToLat($relY2);
  231. $Unit = 360 / (2 ** $Zoom);
  232. my $Long1 = -180 + $X * $Unit;
  233. return ($Lat2, $Long1, $Lat1, $Long1 + $Unit); # S,W,N,E
  234. }
  235.  
  236. sub ProjectMercToLat($){
  237. my $MercY = shift;
  238. return rad2deg(atan(sinh($MercY)));
  239. }
  240. sub ProjectF {
  241. my $Lat = shift;
  242. $Lat = deg2rad($Lat);
  243. my $Y = log(tan($Lat) + sec($Lat));
  244. return $Y;
  245. }
  246.  
  247.  
  248. # sub GeoJsonToProtoBuf {}
  249.  
  250. sub FetchFeaturesForTile {
  251. my $ds = shift;
  252. my $x = shift;
  253. my $y = shift;
  254. my $z = shift;
  255.  
  256. my @features = ();
  257.  
  258. my ($S,$W,$N,$E) = Project($x, $y, $z);
  259. # info("$S,$W,$N,$En");
  260.  
  261. # Connect to the database
  262. my $dbh = DBI->connect("DBI:Pg:dbname=$DBNAME;host=localhost;port=5432", "$DBUSER", "$DBPASS")
  263. or die "Couldn't open database: $DBI::errstr; stopped";
  264. # Prepare the SQL query for execution
  265. # ST_MakeEnvelope(left, bottom, right, top, srid)
  266. my $sth = $dbh->prepare("SELECT longitude, latitude FROM $ds WHERE geom && ST_MakeEnvelope($W, $S, $E, $N, 4326);")
  267. or die "Couldn't prepare statement: $DBI::errstr; stopped";
  268. # Execute the query
  269. $sth->execute()
  270. or die "Couldn't execute statement: $DBI::errstr; stopped";
  271. # Fetch each row and print it
  272. while ( my $row = $sth->fetchrow_hashref ) {
  273. # debug("longitude: $row->{longitude} t latitude: $row->{latitude}");latitude
  274. my $longitude = $row->{longitude};
  275. my $latitude = $row->{latitude};
  276. push(@features, {
  277. longitude => 0+$longitude,
  278. latitude => 0+$latitude
  279. });
  280. }
  281. $sth->finish();
  282. # Disconnect from the database
  283. $dbh->disconnect();
  284.  
  285. return @features;
  286.  
  287. }
  288.  
  289.  
  290. sub writeTileToFile {
  291. my $tile = shift;
  292. open my($fh), ">tile_debug.dat";
  293. binmode $fh;
  294. print $fh $tile;
  295. close $fh;
  296. }
  297.  
  298.  
  299. ######################################
  300. # Http Router
  301. ######################################
  302. # https://github.com/mapbox/vector-tile-spec
  303. any ['get', 'post'] => '/tiles/:datasource/:z/:x/:y' => sub {
  304. my $ds = params->{datasource};
  305. my $x = 0 + params->{x};
  306. my $y = 0 + params->{y};
  307. my $z = 0 + params->{z};
  308.  
  309. push_header "Content-Type" => "application/x-protobuf";
  310. push_header "Access-Control-Allow-Origin" => "*";
  311. push_header "Access-Control-Allow-Methods" => "GET, POST, OPTIONS";
  312.  
  313. my $features = &FetchFeaturesForTile($ds, $x, $y, $z);
  314.  
  315. my @feats;
  316. my $id = 0;
  317. my $extent = $Default_Tile_Layer_Extent;
  318.  
  319. foreach my $feat (@{$features}) {
  320. my @geom;
  321. my $cmd = moveTo(1);
  322. push(@geom, $cmd);
  323.  
  324. my $pX = 0;
  325. my $pY = 0;
  326.  
  327. my $longitude = $feat->{longitude};
  328. my $latitude = $feat->{latitude};
  329. my ($tile_x, $tile_y) = lngLatToTileXY($longitude, $latitude, $x, $y, $z);
  330. # Check if feature inside tile bounds
  331. if ($tile_x >= 0 && $tile_x < 1 && $tile_y >= 0 && $tile_y < 1) {
  332.  
  333. my $deltaX = floor( ($extent * $tile_x + 0.5) ) - $pX;
  334. my $deltaY = floor( ($extent * $tile_y + 0.5) ) - $pY;
  335.  
  336. push(@geom, paramEnc($deltaX));
  337. push(@geom, paramEnc($deltaY));
  338.  
  339. push(@feats, {
  340. id => $id,
  341. type => 1,
  342. geometry => @geom
  343. });
  344.  
  345. $id++;
  346. }
  347.  
  348. }
  349.  
  350. my $tileProtoBuf = Tile->encode({
  351. layers => [
  352. {
  353. version => 2,
  354. # name => $ds,
  355. name => "points",
  356. extent => $extent,
  357. features => @feats
  358. }
  359. ]
  360. });
  361.  
  362. return $tileProtoBuf;
  363. };
  364.  
  365. sub Init {
  366. $APP_CONFIG = getServerConfig($CONFIG_FILE);
  367. print(Dumper($APP_CONFIG->{database}));
  368. }
  369.  
  370. sub Main {
  371. Init();
  372. dance();
  373. }
  374.  
  375. Main();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement