aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJonas Smedegaard <dr@jones.dk>2018-01-04 22:27:35 +0100
committerJonas Smedegaard <dr@jones.dk>2018-01-04 22:27:35 +0100
commitdcebcc6b1b5b03f7fd37864ded04fabdba0a0b57 (patch)
tree7c21a296f649d7453fa749c88386d56eb665ba72
Initial draft.
-rw-r--r--.gitignore3
-rw-r--r--.perltidyrc23
-rw-r--r--.tidyallrc18
-rw-r--r--Makefile40
-rw-r--r--README.md51
-rw-r--r--USE.md78
-rw-r--r--bin/build.js41
-rwxr-xr-xbin/build.psgi21
-rwxr-xr-xbin/src.psgi20
l---------src/css/leaflet/MarkerCluster.Default.css1
l---------src/css/leaflet/MarkerCluster.css1
l---------src/css/leaflet/leaflet.css1
-rw-r--r--src/css/map.css23
-rw-r--r--src/data/staff.json20
l---------src/img/leaflet1
-rw-r--r--src/js/app/mapfactory.js26
-rw-r--r--src/js/app/places.js16
-rw-r--r--src/js/app/position.js21
l---------src/js/lib/leaflet.js1
l---------src/js/lib/leaflet.markercluster.js1
l---------src/js/lib/require.js1
-rw-r--r--src/js/lib/require/json.js84
l---------src/js/lib/require/text.js1
-rw-r--r--src/js/slippymap.js13
-rw-r--r--src/js/world-staff.js18
-rw-r--r--src/world/staff/index.html15
26 files changed, 539 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..ab3f338
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.tidyall.d/
+build/
+/build-*
diff --git a/.perltidyrc b/.perltidyrc
new file mode 100644
index 0000000..d50e6d7
--- /dev/null
+++ b/.perltidyrc
@@ -0,0 +1,23 @@
+# Settings for perltidy
+
+# use best practices, except use of stdout
+--perl-best-practices
+--no-standard-output
+--no-standard-error-output
+
+# use TAB for lead indentation
+--tabs
+--entab-leading-whitespace=4
+-nola
+
+# indent only already indented comments
+--indent-spaced-block-comments
+
+# put brace on new line for named subroutines
+--opening-sub-brace-on-new-line
+
+# preserve horisontally styled lists
+--break-at-old-comma-breakpoints
+
+# overwrite (we use CVS), and leave backup only on error
+--backup-file-extension=/~
diff --git a/.tidyallrc b/.tidyallrc
new file mode 100644
index 0000000..bc3a0cc
--- /dev/null
+++ b/.tidyallrc
@@ -0,0 +1,18 @@
+;; Settings for tidyall
+;: Usage: tidyall -a
+
+[PerlTidy]
+select = **/*.{pl,pm,t,psgi}
+argv = --profile=$ROOT/.perltidyrc
+
+;; TODO: write/extend plugin to support TAB
+;; workaround: jq --tab --sort-keys . < $file | sponge $file
+;; workaround: jq --tab --sort-keys '.features|=sort_by(.geometry.type)|.features|=sort_by(.properties.name)' src/data/staff.json | sponge src/data/staff.json
+;[JSON]
+;select = src/data/**/*.json
+
+;; TODO: package plugin
+;; workaround: js-beautify --end-with-newline --indent-with-tabs --replace $file
+;[JSBeautify]
+;select = src/js/**/*.js bin/*.js
+;ignore = src/js/lib/*
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..0dde3e9
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,40 @@
+pkg-plack-core = libplack-perl
+# TODO: Extend when Plack::Middleware::IndexDir is in stable Debian
+pkg-plack-extra = libplack-middleware-deflater-perl
+# TODO: Use Leaflet.Markercluster only optionally
+pkg-js = \
+ libjs-requirejs libjs-requirejs-text \
+ libjs-leaflet libjs-leaflet-markercluster
+pkg-nodejs = node-requirejs
+pkg-code-minimal = $(pkg-plack-core) $(pkg-js)
+pkg-code-quick = $(pkg-code-minimal) $(pkg-plack-extra)
+pkg-code = $(pkg-code-quick) $(pkg-nodejs) jq
+lists = $(patsubst %,list-%,\
+ pkg-code pkg-code-quick pkg-code-minimal)
+
+all: build-compact
+
+# TODO: Call node (not nodejs) when Nodejs 6 (Debian Buster) is commonly used
+build-compact:
+ nodejs /usr/lib/nodejs/requirejs/r.js -o bin/build.js
+ jq --tab --sort-keys -c '.features|=sort_by(.geometry.type)|.features|=sort_by(.properties.name)' \
+ < build/data/staff.json \
+ > build/data/staff.json~
+ mv -f build/data/staff.json~ build/data/staff.json
+ touch $@
+
+# load httpd service
+serve-quick:
+ bin/src.psgi
+serve-compact:
+ bin/build.psgi
+
+# machine-readable output (e.g. APT package dependencies)
+$(lists): list-%:
+ @echo "$(sort $($*))"
+
+clean:
+ rm -rf build
+ rm -f build-compact
+
+.PHONY: all clean list-% serve-%
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..ff6d93a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,51 @@
+# Maps
+
+This project produces a set of slippy maps.
+
+
+## Overview
+
+This document provides a broad overview of the project.
+
+For detailed information,
+please consult these additional documents:
+
+ * <USE.md> - ways to use of the project as-is, and their requirements
+ * <TODO.md> - future tasks and ideas
+ * <LICENSE.md> - copyright and licensing info
+
+Also, each source file -
+in particular the scripts below <bin> -
+contain specific notes on use and requirements at the top of the file.
+
+
+## Usage
+
+When needed software is installed
+a web service can be started
+and the visualizations accessed from a web browser.
+
+A minimal web service is included
+ready to use for quick preview.
+For large-scale publication
+a production quality service like Apache Httpd is recommended.
+
+See file <USE.md> for detailed information
+on ways to use of the project as-is, and their requirements.
+
+
+## Rights
+
+See file <LICENSE.md> for detailed copyright and licensing info.
+
+Please do not hesitate to ask if in doubt.
+
+
+## Contact
+
+Main developer: Jonas Smedegaard <dr@jones.dk>.
+
+We would love to hear from you,
+whether about uses and reuses of the project,
+interest in helping out to improve some things,
+or just excitement or scepticism, or whatever :-)
diff --git a/USE.md b/USE.md
new file mode 100644
index 0000000..81103d6
--- /dev/null
+++ b/USE.md
@@ -0,0 +1,78 @@
+# Using these slippy maps
+
+This project is usable in several ways:
+
+ * Quick - faster to prepare but loads slower into web browser
+ * Compact - compiled for fast and compact hosting
+
+For both approaches the code must be served via a web server
+(loading from filesystem directly into web browser does not work).
+
+
+## Quick
+
+Quick serving is handy during development,
+as changes can be tested without building everything.
+
+
+### Prerequisites
+
+Show needed packages,
+and install them with APT (i.e. as root):
+
+ make list-pkg-code-quick
+ sudo apt install $ABOVE_PACKAGE_LIST
+
+(or install pkglist-code-minimal for a slightly rough visual result)
+
+
+### Serve
+
+Start a tiny webserver,
+and open a web browser at the local address it tells you:
+
+ make serve-quick
+
+
+## Compact
+
+Compact serving is optimized for production use,
+e.g. on a public host served with Apache.
+
+Code and data gets compiled together,
+optimized for the smallest possible size and complexity
+for each single concrete map.
+
+
+### Prerequisites
+
+Show needed packages,
+and install them with APT (i.e. as root):
+
+ make list-pkg-code
+ sudo apt install $ABOVE_PACKAGE_LIST
+
+
+### Build
+
+To build functional site but skip image fetching:
+
+ make build-compact
+
+To build everything, including image fetching:
+
+ make
+
+
+### Serve
+
+Start a tiny webserver,
+and open a web browser at the local address it tells you:
+
+ make serve-quick
+
+For production use,
+consider to instead setup e.g. Apache to serve <build> directory.
+The <build> directory is fully self-contained
+(contain no symlinks to or html links to system-wide code)
+so can be copied to an external web hosting provider.
diff --git a/bin/build.js b/bin/build.js
new file mode 100644
index 0000000..3b750f5
--- /dev/null
+++ b/bin/build.js
@@ -0,0 +1,41 @@
+{
+ appDir: '../src',
+ mainConfigFile: '../src/js/slippymap.js',
+ dir: '../build',
+ modules: [
+ //First set up the common build layer.
+ {
+ //module names are relative to baseUrl
+ name: '../slippymap',
+ //List common dependencies here. Only need to list
+ //top level dependencies, "include" will find
+ //nested dependencies.
+ include: [
+ 'leaflet',
+ 'app/mapfactory',
+ 'app/places',
+ 'app/position'
+ ]
+ },
+
+ //Now set up a build layer for each page, but exclude
+ //the common one. "exclude" will exclude
+ //the nested, built dependencies from "slippymap". Any
+ //"exclude" that includes built modules should be
+ //listed before the build layer that wants to exclude it.
+ //"include" the appropriate "app/main*" module since by default
+ //it will not get added to the build since it is loaded by a nested
+ //requirejs in the page*.js files.
+ {
+ name: '../world-staff',
+ include: [
+ 'json!data/staff.json'
+ ],
+ exclude: ['../slippymap']
+ }
+
+ ],
+ optimize: "uglify2",
+ optimizeCss: "standard.keepLines",
+ removeCombined: true,
+}
diff --git a/bin/build.psgi b/bin/build.psgi
new file mode 100755
index 0000000..cdc51ef
--- /dev/null
+++ b/bin/build.psgi
@@ -0,0 +1,21 @@
+#!/usr/bin/env plackup
+
+use strict;
+use warnings;
+
+use FindBin qw($Bin);
+
+use Plack::Builder;
+
+#use Plack::App::File;
+use Plack::App::Directory;
+
+builder {
+ eval { enable 'DirIndex' };
+ eval { enable 'Deflater' };
+
+# enable 'Debug', panels => [ qw(DBITrace Memory Timer) ];
+# mount '/usr/share/javascript' => Plack::App::File->new( root => '/usr/share/javascript' )->to_app;
+ mount '/' =>
+ Plack::App::Directory->new( root => "$Bin/../build" )->to_app;
+};
diff --git a/bin/src.psgi b/bin/src.psgi
new file mode 100755
index 0000000..44e557a
--- /dev/null
+++ b/bin/src.psgi
@@ -0,0 +1,20 @@
+#!/usr/bin/env plackup
+
+use strict;
+use warnings;
+
+use FindBin qw($Bin);
+
+use Plack::Builder;
+
+#use Plack::App::File;
+use Plack::App::Directory;
+
+builder {
+ eval { enable 'DirIndex' };
+ eval { enable 'Deflater' };
+
+# enable 'Debug', panels => [ qw(DBITrace Memory Timer) ];
+# mount '/usr/share/javascript' => Plack::App::File->new( root => '/usr/share/javascript' )->to_app;
+ mount '/' => Plack::App::Directory->new( root => "$Bin/../src" )->to_app;
+};
diff --git a/src/css/leaflet/MarkerCluster.Default.css b/src/css/leaflet/MarkerCluster.Default.css
new file mode 120000
index 0000000..2cf500e
--- /dev/null
+++ b/src/css/leaflet/MarkerCluster.Default.css
@@ -0,0 +1 @@
+/usr/share/javascript/leaflet/MarkerCluster.Default.css \ No newline at end of file
diff --git a/src/css/leaflet/MarkerCluster.css b/src/css/leaflet/MarkerCluster.css
new file mode 120000
index 0000000..a791781
--- /dev/null
+++ b/src/css/leaflet/MarkerCluster.css
@@ -0,0 +1 @@
+/usr/share/javascript/leaflet/MarkerCluster.css \ No newline at end of file
diff --git a/src/css/leaflet/leaflet.css b/src/css/leaflet/leaflet.css
new file mode 120000
index 0000000..40a1f03
--- /dev/null
+++ b/src/css/leaflet/leaflet.css
@@ -0,0 +1 @@
+/usr/share/javascript/leaflet/leaflet.css \ No newline at end of file
diff --git a/src/css/map.css b/src/css/map.css
new file mode 100644
index 0000000..626ff84
--- /dev/null
+++ b/src/css/map.css
@@ -0,0 +1,23 @@
+.leaflet-control-layers-toggle {
+ background-image: url(../img/leaflet/layers.png);
+}
+
+.leaflet-retina .leaflet-control-layers-toggle {
+ background-image: url(../img/leaflet/layers-2x.png);
+}
+
+#content {
+ position: absolute;
+ top: 0;
+ left: 0;
+ height: 100%;
+ width: 100%;
+}
+
+.leaflet-control-scale {
+ text-align: center;
+}
+
+.info {
+ background-color: white;
+}
diff --git a/src/data/staff.json b/src/data/staff.json
new file mode 100644
index 0000000..c103523
--- /dev/null
+++ b/src/data/staff.json
@@ -0,0 +1,20 @@
+{
+ "features": [
+ {
+ "geometry": {
+ "coordinates": [
+ 11.81707,
+ 55.76429
+ ],
+ "type": "Point"
+ },
+ "properties": {
+ "id": "jonas.smedegaard",
+ "name": "Jonas Smedegaard",
+ "title": "PureOS Developer"
+ },
+ "type": "Feature"
+ }
+ ],
+ "type": "FeatureCollection"
+}
diff --git a/src/img/leaflet b/src/img/leaflet
new file mode 120000
index 0000000..a962773
--- /dev/null
+++ b/src/img/leaflet
@@ -0,0 +1 @@
+/usr/share/javascript/leaflet/images \ No newline at end of file
diff --git a/src/js/app/mapfactory.js b/src/js/app/mapfactory.js
new file mode 100644
index 0000000..8d8bb0e
--- /dev/null
+++ b/src/js/app/mapfactory.js
@@ -0,0 +1,26 @@
+define(['leaflet'], function(L) {
+ // base config
+ var attribOSM = '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
+ OSMLayer = L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ attribution: attribOSM
+ }),
+ scale = L.control.scale({
+ imperial: false
+ });
+
+ return function(id, bounds) {
+ var map = L.map(id, {
+ layers: [OSMLayer]
+ })
+ if (bounds) {
+ map.fitBounds(L.latLngBounds(bounds));
+ } else {
+ map.fitWorld().zoomIn();
+ }
+ map.attributionControl.setPrefix(false);
+
+ scale.addTo(map);
+
+ return map;
+ };
+});
diff --git a/src/js/app/places.js b/src/js/app/places.js
new file mode 100644
index 0000000..9b915cb
--- /dev/null
+++ b/src/js/app/places.js
@@ -0,0 +1,16 @@
+define(['leaflet'], function(L) {
+
+ // GeoJSON feature grouping
+ function returnMarker(feature, latlng) {
+ return L.marker(latlng);
+ };
+
+ var place = L.geoJson([], {
+ pointToLayer: returnMarker
+ });
+
+ return function(data) {
+ place.addData(data);
+ return L.layerGroup().addLayer(place);
+ };
+});
diff --git a/src/js/app/position.js b/src/js/app/position.js
new file mode 100644
index 0000000..0e9bfb2
--- /dev/null
+++ b/src/js/app/position.js
@@ -0,0 +1,21 @@
+define(['leaflet'], function(L) {
+
+ // position popup
+ function round(n,d) {
+ return Math.round(Math.pow(10,d)*n)/Math.pow(10,d)
+ };
+ function lngLatString(latLng) {
+ return round(latLng.lng,5) + ", " + round(latLng.lat,5)
+ };
+ var popup = L.popup();
+
+ return function positionHook(map) {
+ function positionPopup(e) {
+ popup
+ .setLatLng(e.latlng)
+ .setContent("Position (long, lat):<br>" + lngLatString(e.latlng))
+ .openOn(map);
+ }
+ map.on('contextmenu', positionPopup);
+ }
+});
diff --git a/src/js/lib/leaflet.js b/src/js/lib/leaflet.js
new file mode 120000
index 0000000..f0df49d
--- /dev/null
+++ b/src/js/lib/leaflet.js
@@ -0,0 +1 @@
+/usr/share/javascript/leaflet/leaflet.js \ No newline at end of file
diff --git a/src/js/lib/leaflet.markercluster.js b/src/js/lib/leaflet.markercluster.js
new file mode 120000
index 0000000..881bcea
--- /dev/null
+++ b/src/js/lib/leaflet.markercluster.js
@@ -0,0 +1 @@
+/usr/share/javascript/leaflet/leaflet.markercluster.js \ No newline at end of file
diff --git a/src/js/lib/require.js b/src/js/lib/require.js
new file mode 120000
index 0000000..a2b8052
--- /dev/null
+++ b/src/js/lib/require.js
@@ -0,0 +1 @@
+/usr/share/javascript/requirejs/require.js \ No newline at end of file
diff --git a/src/js/lib/require/json.js b/src/js/lib/require/json.js
new file mode 100644
index 0000000..40e4da1
--- /dev/null
+++ b/src/js/lib/require/json.js
@@ -0,0 +1,84 @@
+/** @license
+ * RequireJS plugin for loading JSON files
+ * - depends on Text plugin and it was HEAVILY "inspired" by it as well.
+ * Author: Miller Medeiros
+ * Version: 0.4.0 (2014/04/10)
+ * Released under the MIT license
+ *
+ * Patched (2013/10/10):
+ * - supports JS-like comments which are beginning from /* or //
+ */
+define(['text'], function (text) {
+
+ var CACHE_BUST_QUERY_PARAM = 'bust',
+ CACHE_BUST_FLAG = '!bust',
+ jsonParse = (typeof JSON !== 'undefined' && typeof JSON.parse === 'function') ? JSON.parse : function (val) {
+ return eval('(' + val + ')'); //quick and dirty
+ },
+ PROTECTION_PREFIX = /^\)\]\}',?\n/,
+ buildMap = {};
+
+ function cacheBust(url) {
+ url = url.replace(CACHE_BUST_FLAG, '');
+ url += (url.indexOf('?') < 0) ? '?' : '&';
+ return url + CACHE_BUST_QUERY_PARAM + '=' + Math.round(2147483647 * Math.random());
+ }
+
+ //API
+ return {
+ load: function(name, req, onLoad, config) {
+ // Make sure file part of url ends with .json, add it if not
+ name = name.replace(new RegExp("^[^?]*"), function(base) {
+ return base.substr(-5) === ".json" ? base : base + ".json";
+ });
+ var url = req.toUrl(name);
+ if (config.isBuild && (config.inlineJSON === false || name.indexOf(CACHE_BUST_QUERY_PARAM + '=') !== -1)) {
+ //avoid inlining cache busted JSON or if inlineJSON:false
+ onLoad(null);
+ } else if (url.indexOf('empty:') === 0) {
+ //and don't inline files marked as empty: urls
+ onLoad(null);
+ } else {
+ text.get(url,
+ function (data) {
+ // Need to check if the JSON data has been formatted for the JSON array security vulnerability
+ var cleaned_data = ('' + data).replace(PROTECTION_PREFIX, '');
+ cleaned_data = cleaned_data.replace(/\/\*.+?\*\/|\/\/[^\n\r]*/g, '');
+ var parsed = null;
+ try {
+ parsed = jsonParse(cleaned_data);
+ if (config.isBuild) {
+ buildMap[name] = parsed;
+ }
+ onLoad(parsed);
+ } catch (e) {
+ onLoad.error(e);
+ //onLoad(null); -- should we really call onLoad???
+ }
+ },
+ onLoad.error, {
+ accept: 'application/json'
+ }
+ );
+ }
+ },
+
+ normalize: function (name, normalize) {
+ // used normalize to avoid caching references to a "cache busted" request
+ if (name.indexOf(CACHE_BUST_FLAG) !== -1) {
+ name = cacheBust(name);
+ }
+ // resolve any relative paths
+ return normalize(name);
+ },
+
+ // write method based on RequireJS official text plugin by James Burke
+ // https://github.com/jrburke/requirejs/blob/master/text.js
+ write: function (pluginName, moduleName, write) {
+ if (moduleName in buildMap) {
+ var content = buildMap[moduleName];
+ write('define("' + pluginName + '!' + moduleName + '", function () { return ' + (content ? JSON.stringify(content) : content) + '; });\n');
+ }
+ }
+ };
+});
diff --git a/src/js/lib/require/text.js b/src/js/lib/require/text.js
new file mode 120000
index 0000000..2b490b4
--- /dev/null
+++ b/src/js/lib/require/text.js
@@ -0,0 +1 @@
+/usr/share/javascript/requirejs/text.js \ No newline at end of file
diff --git a/src/js/slippymap.js b/src/js/slippymap.js
new file mode 100644
index 0000000..ac76efd
--- /dev/null
+++ b/src/js/slippymap.js
@@ -0,0 +1,13 @@
+// shared code common across pages
+requirejs.config({
+ baseUrl: 'js/lib',
+ paths: {
+ text: 'require/text',
+ json: 'require/json',
+ app: '../app',
+ data: '../../data'
+ },
+ shim: {
+ 'leaflet.markercluster': ['leaflet']
+ }
+});
diff --git a/src/js/world-staff.js b/src/js/world-staff.js
new file mode 100644
index 0000000..618c2fd
--- /dev/null
+++ b/src/js/world-staff.js
@@ -0,0 +1,18 @@
+//Load common code that includes config, then load the app logic for this page.
+requirejs(['./slippymap'], function(_foo) {
+ requirejs.config({
+ baseUrl: '../../js/lib',
+ });
+ L.Icon.Default.imagePath = '../../img/leaflet';
+ requirejs(['app/mapfactory'], function(mkmap) {
+ var map = mkmap('content');
+ requirejs([
+ 'app/places',
+ 'json!data/staff.json',
+ 'app/position'
+ ], function(places, data, hook) {
+ map.addLayer(places(data));
+ hook(map);
+ });
+ });
+});
diff --git a/src/world/staff/index.html b/src/world/staff/index.html
new file mode 100644
index 0000000..4782775
--- /dev/null
+++ b/src/world/staff/index.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>Staff, worldwide</title>
+<meta name="viewport" content="width=device-width" />
+<link href="../../favicon.ico" rel="icon" type="image/x-icon" />
+<link href="../../css/leaflet/leaflet.css" rel="stylesheet" type="text/css" />
+<link href="../../css/map.css" rel="stylesheet" type="text/css" />
+</head>
+<body>
+<div id="content"></div>
+<script src="../../js/lib/require.js" data-main="../../js/world-staff" type="text/javascript"></script>
+</body>
+</html>