ctucx.git: gpx-map

render gpx files to tiles and display them on a map

commit b94fc23afb7cb8f5c67c7b95751ae12d9c807cf9
parent c2a97b18f868be8c4493419f0fc27a035fdd8316
Author: Leah (ctucx) <git@ctu.cx>
Date: Sun, 11 Dec 2022 11:02:32 +0100

flake.nix: add scripts for tiles generation
3 files changed, 170 insertions(+), 8 deletions(-)
M
flake.nix
|
106
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
M
package.json
|
2
+-
A
parse-gpx.pl
|
70
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
diff --git a/flake.nix b/flake.nix
@@ -1,5 +1,5 @@
 {
-  description = "";
+  description = "render gpx files to tiles and display them on a map";
 
   inputs = {
     flake-utils.url = "github:numtide/flake-utils";

@@ -10,8 +10,26 @@
 
     overlay = final: prev: {
 
-      bikemap = final.mkYarnPackage rec {
-        name = "bikemap";
+      datamaps  = final.stdenv.mkDerivation {
+        name = "datamaps";
+
+        src = final.fetchFromGitHub {
+          owner  = "e-n-f";
+          repo   = "datamaps";
+          rev    = "76e620adabbedabd6866b23b30c145b53bae751e";
+          sha256 = "1rdqbyfmgidiv4aqy1s6llls304dxbg5226c7k622smd2rnda2jk";
+        };
+
+        buildInputs = with final; [ pkgconfig libpng ];
+
+        installPhase = ''
+          mkdir -p $out/bin;
+          cp {encode,render,merge,enumerate} $out/bin;
+        '';
+      };
+
+      gpx-map = final.mkYarnPackage {
+        name = "gpx-map";
         src  = self;
 
         packageJSON = ./package.json;

@@ -26,10 +44,78 @@
 
         installPhase = ''
           mkdir -p $out
-          cp -r deps/bikemap/{index.html,bundle.js} $out
+          cp -r deps/gpx-map/{index.html,bundle.js} $out
         '';
       };
 
+      generateTilesFromGPX = (
+        let
+          parse-gpx = final.stdenv.mkDerivation rec {
+            name = "parse-gpx";
+            src  = ./parse-gpx.pl;
+
+            dontUnpack = true;
+
+            nativeBuildInputs = [ final.makeWrapper ];
+            buildInputs       = [ final.perl ];
+
+            installPhase = ''mkdir -p $out/bin; cp $src $out/bin/parse-gpx; chmod +x $out/bin/parse-gpx;'';
+            postFixup    = ''wrapProgram $out/bin/parse-gpx --prefix PERL5LIB : "${with final.perlPackages; makePerlPath [ XMLParser ]}"'';
+          };
+
+         makeTile = final.writeShellScript "makeTile.sh" ''
+           mkdir -p tiles/$2/$3
+           echo "rendering $1 $2 $3 $4 $5 $6"
+
+           if [ $2 -gt 13 ];
+           then
+             ${final.datamaps}/bin/render -g -t0 -L4 -c 'ff8800' -S 'ff8800' $1 $2 $3 $4 | ${final.pngquant}/bin/pngquant 256 > tiles/$2/$3/$4.png
+           else
+             ${final.datamaps}/bin/render -g -t0 -L7 -c 'ff8800' -S 'ff8800' $1 $2 $3 $4 | ${final.pngquant}/bin/pngquant 256 > tiles/$2/$3/$4.png
+           fi
+         '';
+
+        in final.writeShellScriptBin "generateTilesFromGPX" ''
+          # strict mode
+          set -euo pipefail
+          IFS=$'\n\t'
+
+          TMP_DIR=`mktemp -d -t tiles.XXXXXXXXXX`
+
+          trap "{ rm -rf "$TMP_DIR"; }" SIGINT SIGTERM ERR EXIT
+
+          if [ ! -d "$1" ]; then
+            echo "$1 does not exist."
+            exit 1
+          fi
+
+          if [ -z "$(ls -A $1)" ]; then
+            echo "$1 is empty."
+            exit 1
+          fi
+
+          if [ ! -d "$2" ]; then
+            echo "$2 does not exist."
+            exit 1
+          fi
+
+          if [ ! -z "$(ls -A $2)" ]; then
+            echo "$2 is not empty."
+            exit 1
+          fi
+
+          GPX_DIR=`realpath $1`
+          TILES_DIR=`realpath $2`
+
+          cd $TMP_DIR
+
+          find $GPX_DIR -name '*.gpx' -print0 | xargs -0 ${parse-gpx}/bin/parse-gpx | ${final.datamaps}/bin/encode -z16 -m8 -o $TMP_DIR/gpx.dm
+          ${final.datamaps}/bin/enumerate -s -Z6 -z16 ./gpx.dm | xargs -L1 -P3 ${makeTile}
+
+          mv $TMP_DIR/tiles/* $TILES_DIR/
+        ''
+      );
+
     };
 
   } // (flake-utils.lib.eachDefaultSystem (system:

@@ -41,17 +127,23 @@
 
     in rec {
 
-      packages.bikemap = pkgs.bikemap;
-      packages.default = pkgs.bikemap;
+      packages.generateTilesFromGPX = pkgs.generateTilesFromGPX;
+      packages.datamaps             = pkgs.datamaps;
+      packages.gpx-map               = pkgs.gpx-map;
  
       devShells.default = pkgs.mkShell {
-        nativeBuildInputs = [ pkgs.yarn pkgs.yarn2nix pkgs.php ];
+        nativeBuildInputs = with pkgs; [ yarn yarn2nix php datamaps generateTilesFromGPX ];
         shellHook = ''
           export NODE_OPTIONS=--openssl-legacy-provider
           alias serve="yarn build && php -S localhost:8080"
         '';
       };
 
+      apps.default = {
+        type = "app";
+        program = "${pkgs.generateTilesFromGPX}/bin/generateTilesFromGPX";
+      };
+
     }
   ));
 } 
\ No newline at end of file
diff --git a/package.json b/package.json
@@ -1,5 +1,5 @@
 {
-  "name": "bikemap",
+  "name": "gpx-map",
   "version": "1.0.0",
   "description": "Just a simple map with 2 layers",
   "scripts": {
diff --git a/parse-gpx.pl b/parse-gpx.pl
@@ -0,0 +1,70 @@
+#!/usr/bin/env perl
+use POSIX;
+use XML::Parser;
+
+$pi = 4 * atan2(1, 1);
+
+sub handle_start {
+	my ($expat, $element, @tags) = @_;
+	my %tags = @tags;
+
+	if ($element eq "trkpt") {
+		$lat = $tags{'lat'};
+		$lon = $tags{'lon'};
+	} elsif ($element eq "ele") {
+		$state = "ele";
+	} elsif ($element eq "time") {
+		$state = "time";
+	} else {
+		$state = "";
+	}
+}
+
+sub handle_char {
+	my ($expat, $string) = @_;
+
+	$text{$state} .= $string;
+}
+
+sub handle_end {
+	my ($expat, $element) = @_;
+
+	if ($element eq "trkseg" || $element eq "trk") {
+		$oldlat = $oldlon = "";
+	} elsif ($element eq "trkpt") {
+		if ($lat ne "" && $lon ne "" && $oldlat ne "" && $oldlon ne "") {
+			if ($lat ne $oldlat || $lon ne $oldlon) {
+				print "$oldlat,$oldlon $lat,$lon ";
+
+				$rat = cos(($lat + $oldlat) / 2 * $pi / 180);
+				$ang = atan2($lat - $oldlat, ($lon - $oldlon) * $rat);
+
+				printf("8:%d\n", ($ang + $pi) * 256 / (2 * $pi));
+			}
+		}
+
+		$oldlat = $lat;
+		$oldlon = $lon;
+		%text = ();
+	}
+}
+
+if ($#ARGV < 0) {
+	$parser = new XML::Parser(Handlers => { Start => \&handle_start,
+						End   => \&handle_end,
+						Char  => \&handle_char,
+					      });
+
+	$parser->parse(*STDIN);
+} else {
+	for $file (@ARGV) {
+		$oldwhen = 0;
+
+		$parser = new XML::Parser(Handlers => { Start => \&handle_start,
+							End   => \&handle_end,
+							Char  => \&handle_char,
+						      });
+
+		$parser->parsefile($file);
+	}
+}