Add Space Weaver -- vector map application.

This really needs more work (as documented in README), but this is
already quite useful.

Please check app.js file -- I used library for conversion between xyz
and lat/lon, so its license applies. It seems to be compatible with
bangle apps license.
pull/2981/head
Pavel Machek 2023-08-18 14:17:53 +02:00
parent f67d9eb85f
commit 3158d945aa
10 changed files with 995 additions and 0 deletions

43
apps/spacew/README.md Normal file
View File

@ -0,0 +1,43 @@
# Space Weaver ![](app.png)
Vector map
Written by: [Pavel Machek](https://github.com/pavelmachek)
Space Weaver is application for displaying vector maps. It is
currently suitable for developers, and more work is needed.
Maps can be created from openstreetmap extracts. Those are cut using
osmosis, then translated into geojson. Geojson is further processes to
add metadata such as colors, and to split it into xyz tiles, while
keeping geojson format. Tiles are then merged into single file, which
can be uploaded to the filesystem. Index at the end provides locations
of the tiles.
## Preparing data
Tools in spacew/prep can be used to prepare data.
You'll need to edit prepare.sh to point it to suitable osm extract,
and you'll need to select area of interest. Start experiments with
small area. You may want to delete cstocs and provide custom
conversion to ascii.
Details of which features are visible at what zoom levels can be
configured in split.js. This can greatly affect file sizes. Then
there's "meta.max_zoom = 17" setting, reduce it if file is too big.
For initial experiments, configure things so that mtar file is around
500KB. (I had troubles with big files, both on hardware and to lesser
extent on simulator. In particular, mtar seemed to be corrupted after
emulator window was closed.)
## Future Development
Directories at the end of .mtar should be hashed, not linear searched.
Geojson is not really suitable as it takes a lot of storage.
It would be nice to support polygons.
Web-based tool for preparing maps would be nice.

2
apps/spacew/app-icon.js Normal file
View File

@ -0,0 +1,2 @@
require("heatshrink").decompress(atob("mEwwkE/8Ql//j//AAUD//yAgILBAAXzBQMxAoMwn4XKBIgXCmAXEh4XF+IJGC4XxAoMgl/zgX/nASBBgPwIoIEBmYBBI4ug1/6hX/zOf+UBEIMP+UC+eZAIPyhP/yAnB0Ef+QXBnM/GgUwh4ECwX/wYvCkIvB+BrBA4JsFAQMiL4gRBA4XxNYIlBBgQGBiJXEBQRnBiYoEiQXFgURT4YAB+QXBS4RTCJoQMBj4gBWQPwN4IKCNgLHDRAIlDEgIxBC4zHBJITACC4gMB+MfAIJCCRIU/GIIGCEoLyCBgQOCgZAEBAL5CC4UvC4oFBMIJ9CCAQMBPwbABKoYMBJ4KpBZQgKBVwnyh/wKoQMBVoUgn4XFmTGEgfxC4QKBCQRKBeAYtBkYXFXYIFBkTfCSgMfIIYbBdwTADJIIEBkYEDAYKyDC4J9DKoSFDiZMDGYKCDkbWEKoUzIQQREHQIFDifzBQYXGIIIMDkDwDN4IXFIIIXBJQMhEQqCCT4IWENoUCC4MvXwTjCiZqBEQIXGNoITBC4LRDEQMDHQbWEAAUDIYPzmabEEQIXDmYXGiUgFAMyLASQDXgPzj7uEQobNB+MxWYsgHQKSBEQqFCUYPwUwgKCHQUvEQqFCkAXCBQ0Qn/xmYXH+IXB+S+ESAUAEQMzHQqFCgEvmS+EBQUBl/wUw4MDmS+ESAcf+ExC44MCmS+ESAcPmAvI/8hh8iNY8wgcwaw4MCh8hNY/wC5kDTwKbHgThGEgsQQZMhdw61CgSmGAAUANRAkCgUTBZEQiRSHHga+HNYUCC5I8BXw4XCgIWJHgJTJ+IXJHAIXB+eTJoIJD+fyC4LABYQWZBQOYC4Mf+eS/85DgIJBxMygAFB+YUBC4YqBkAoBAIM5n4JCAgIvBwYBCNgyDKTRIXM+YXFA="))

620
apps/spacew/app.js Normal file
View File

@ -0,0 +1,620 @@
/* original openstmap.js */
/* OpenStreetMap plotting module.
Usage:
var m = require("openstmap");
// m.lat/lon are now the center of the loaded map
m.draw(); // draw centered on the middle of the loaded map
// plot gps position on map
Bangle.on('GPS',function(f) {
if (!f.fix) return;
var p = m.latLonToXY(fix.lat, fix.lon);
g.fillRect(p.x-2, p.y-2, p.x+2, p.y+2);
});
// recenter and redraw map!
function center() {
m.lat = fix.lat;
m.lon = fix.lon;
m.draw();
}
// you can even change the scale - eg 'm/scale *= 2'
*/
var exports = {};
var m = exports;
m.maps = require("Storage").list(/openstmap\.\d+\.json/).map(f=>{
let map = require("Storage").readJSON(f);
map.center = Bangle.project({lat:map.lat,lon:map.lon});
return map;
});
// we base our start position on the middle of the first map
if (m.maps[0] != undefined) {
m.map = m.maps[0];
m.scale = m.map.scale; // current scale (based on first map)
m.lat = m.map.lat; // position of middle of screen
m.lon = m.map.lon; // position of middle of screen
} else {
m.scale = 20;
m.lat = 50;
m.lon = 14;
}
exports.draw = function() {
var cx = g.getWidth()/2;
var cy = g.getHeight()/2;
var p = Bangle.project({lat:m.lat,lon:m.lon});
m.maps.forEach((map,idx) => {
var d = map.scale/m.scale;
var ix = (p.x-map.center.x)/m.scale + (map.imgx*d/2) - cx;
var iy = (map.center.y-p.y)/m.scale + (map.imgy*d/2) - cy;
var o = {};
var s = map.tilesize;
if (d!=1) { // if the two are different, add scaling
s *= d;
o.scale = d;
}
//console.log(ix,iy);
var tx = 0|(ix/s);
var ty = 0|(iy/s);
var ox = (tx*s)-ix;
var oy = (ty*s)-iy;
var img = require("Storage").read(map.fn);
// fix out of range so we don't have to iterate over them
if (tx<0) {
ox+=s*-tx;
tx=0;
}
if (ty<0) {
oy+=s*-ty;
ty=0;
}
var mx = g.getWidth();
var my = g.getHeight();
for (var x=ox,ttx=tx; x<mx && ttx<map.w; x+=s,ttx++)
for (var y=oy,tty=ty;y<my && tty<map.h;y+=s,tty++) {
o.frame = ttx+(tty*map.w);
g.drawImage(img,x,y,o);
}
});
};
/// Convert lat/lon to pixels on the screen
exports.latLonToXY = function(lat, lon) {
var p = Bangle.project({lat:m.lat,lon:m.lon});
var q = Bangle.project({lat:lat, lon:lon});
var cx = g.getWidth()/2;
var cy = g.getHeight()/2;
return {
x : (q.x-p.x)/m.scale + cx,
y : cy - (q.y-p.y)/m.scale
};
};
/// Given an amount to scroll in pixels on the screen, adjust the lat/lon of the map to match
exports.scroll = function(x,y) {
var a = Bangle.project({lat:m.lat,lon:m.lon});
var b = Bangle.project({lat:m.lat+1,lon:m.lon+1});
this.lon += x * m.scale / (a.x-b.x);
this.lat -= y * m.scale / (a.y-b.y);
};
/*
Copyright (c) 2011, Development Seed
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.
- Neither the name "Development Seed" nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// Convert lon lat to screen pixel value
//
// - `ll` {Array} `[lon, lat]` array of geographic coordinates.
// - `zoom` {Number} zoom level.
function px(ll, zoom) {
var size = 256 * Math.pow(2, zoom);
var d = size / 2;
var bc = (size / 360);
var cc = (size / (2 * Math.PI));
var ac = size;
var D2R = Math.PI / 180;
var f = Math.min(Math.max(Math.sin(D2R * ll[1]), -0.9999), 0.9999);
var x = d + ll[0] * bc;
var y = d + 0.5 * Math.log((1 + f) / (1 - f)) * -cc;
var expansion = 1;
(x > ac * expansion) && (x = ac * expansion);
(y > ac) && (y = ac);
//(x < 0) && (x = 0);
//(y < 0) && (y = 0);
return [x, y];
}
// Convert bbox to xyx bounds
//
// - `bbox` {Number} bbox in the form `[w, s, e, n]`.
// - `zoom` {Number} zoom.
// - `tms_style` {Boolean} whether to compute using tms-style.
// - `srs` {String} projection of input bbox (WGS84|900913).
// - `@return` {Object} XYZ bounds containing minX, maxX, minY, maxY properties.
xyz = function(bbox, zoom, tms_style, srs) {
// If web mercator provided reproject to WGS84.
if (srs === '900913') {
bbox = this.convert(bbox, 'WGS84');
}
var ll = [bbox[0], bbox[1]]; // lower left
var ur = [bbox[2], bbox[3]]; // upper right
var px_ll = px(ll, zoom);
var px_ur = px(ur, zoom);
// Y = 0 for XYZ is the top hence minY uses px_ur[1].
var size = 256;
var x = [ Math.floor(px_ll[0] / size), Math.floor((px_ur[0] - 1) / size) ];
var y = [ Math.floor(px_ur[1] / size), Math.floor((px_ll[1] - 1) / size) ];
var bounds = {
minX: Math.min.apply(Math, x) < 0 ? 0 : Math.min.apply(Math, x),
minY: Math.min.apply(Math, y) < 0 ? 0 : Math.min.apply(Math, y),
maxX: Math.max.apply(Math, x),
maxY: Math.max.apply(Math, y)
};
if (tms_style) {
var tms = {
minY: (Math.pow(2, zoom) - 1) - bounds.maxY,
maxY: (Math.pow(2, zoom) - 1) - bounds.minY
};
bounds.minY = tms.minY;
bounds.maxY = tms.maxY;
}
return bounds;
};
// Convert screen pixel value to lon lat
//
// - `px` {Array} `[x, y]` array of geographic coordinates.
// - `zoom` {Number} zoom level.
function ll(px, zoom) {
var size = 256 * Math.pow(2, zoom);
var bc = (size / 360);
var cc = (size / (2 * Math.PI));
var zc = size / 2;
var g = (px[1] - zc) / -cc;
var lon = (px[0] - zc) / bc;
var R2D = 180 / Math.PI;
var lat = R2D * (2 * Math.atan(Math.exp(g)) - 0.5 * Math.PI);
return [lon, lat];
}
// Convert tile xyz value to bbox of the form `[w, s, e, n]`
//
// - `x` {Number} x (longitude) number.
// - `y` {Number} y (latitude) number.
// - `zoom` {Number} zoom.
// - `tms_style` {Boolean} whether to compute using tms-style.
// - `srs` {String} projection for resulting bbox (WGS84|900913).
// - `return` {Array} bbox array of values in form `[w, s, e, n]`.
bbox = function(x, y, zoom, tms_style, srs) {
var size = 256;
// Convert xyz into bbox with srs WGS84
if (tms_style) {
y = (Math.pow(2, zoom) - 1) - y;
}
// Use +y to make sure it's a number to avoid inadvertent concatenation.
var ll_ = [x * size, (+y + 1) * size]; // lower left
// Use +x to make sure it's a number to avoid inadvertent concatenation.
var ur = [(+x + 1) * size, y * size]; // upper right
var bbox = ll(ll_, zoom).concat(ll(ur, zoom));
// If web mercator requested reproject to 900913.
if (srs === '900913') {
return this.convert(bbox, '900913');
} else {
return bbox;
}
};
/* original openstmap_app.js */
//var m = require("openstmap");
var HASWIDGETS = true;
var R;
var fix = {};
var mapVisible = false;
var hasScrolled = false;
var settings = require("Storage").readJSON("openstmap.json",1)||{};
var points;
var startDrag = 0;
// Redraw the whole page
function redraw(qual) {
if (1) drawAll(qual);
g.setClipRect(R.x,R.y,R.x2,R.y2);
if (0) m.draw();
drawPOI();
drawMarker();
// if track drawing is enabled...
if (settings.drawTrack) {
if (HASWIDGETS && WIDGETS["gpsrec"] && WIDGETS["gpsrec"].plotTrack) {
g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later
WIDGETS["gpsrec"].plotTrack(m);
}
if (HASWIDGETS && WIDGETS["recorder"] && WIDGETS["recorder"].plotTrack) {
g.setColor("#f00").flip(); // force immediate draw on double-buffered screens - track will update later
WIDGETS["recorder"].plotTrack(m);
}
}
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
}
// Draw the POIs
function drawPOI() {
if (1) return;
/* var waypoints = require("waypoints").load(); FIXME */ g.setFont("Vector", 18);
waypoints.forEach((wp, idx) => {
var p = m.latLonToXY(wp.lat, wp.lon);
var sz = 2;
g.setColor(0,0,0);
g.fillRect(p.x-sz, p.y-sz, p.x+sz, p.y+sz);
g.setColor(0,0,0);
g.drawString(wp.name, p.x, p.y);
print(wp.name);
})
}
// Draw the marker for where we are
function drawMarker() {
if (!fix.fix) return;
var p = m.latLonToXY(fix.lat, fix.lon);
g.setColor(1,0,0);
g.fillRect(p.x-2, p.y-2, p.x+2, p.y+2);
}
Bangle.on('GPS',function(f) {
fix=f;
if (HASWIDGETS) WIDGETS["sats"].draw(WIDGETS["sats"]);
if (mapVisible) drawMarker();
});
Bangle.setGPSPower(1, "app");
if (HASWIDGETS) {
Bangle.loadWidgets();
WIDGETS["sats"] = { area:"tl", width:48, draw:w=>{
var txt = (0|fix.satellites)+" Sats";
if (!fix.fix) txt += "\nNO FIX";
g.reset().setFont("6x8").setFontAlign(0,0)
.drawString(txt,w.x+24,w.y+12);
}
};
Bangle.drawWidgets();
}
R = Bangle.appRect;
function showMap() {
mapVisible = true;
g.reset().clearRect(R);
redraw(0);
emptyMap();
}
function emptyMap() {
Bangle.setUI({mode:"custom",drag:e=>{
if (e.b) {
if (!startDrag)
startDrag = getTime();
g.setClipRect(R.x,R.y,R.x2,R.y2);
g.scroll(e.dx,e.dy);
m.scroll(e.dx,e.dy);
g.setClipRect(0,0,g.getWidth()-1,g.getHeight()-1);
hasScrolled = true;
print("Has scrolled");
} else if (hasScrolled) {
delta = getTime() - startDrag;
startDrag = 0;
hasScrolled = false;
print("Done", delta, e.x, e.y);
qual = 0;
if (delta < 0.2) {
if (e.x < g.getWidth() / 2) {
if (e.y < g.getHeight() / 2) {
m.scale /= 2;
} else {
m.scale *= 2;
}
} else {
if (e.y < g.getHeight() / 2) {
qual = 2;
} else {
qual = 4;
}
}
}
g.reset().clearRect(R);
redraw(qual);
}
}, btn: btn=>{
mapVisible = false;
var menu = {"":{title:"Map"},
"< Back": ()=> showMap(),
/*LANG*/"Zoom In": () =>{
m.scale /= 2;
showMap();
},
/*LANG*/"Zoom Out": () =>{
m.scale *= 2;
showMap();
},
/*LANG*/"Draw Track": {
value : !!settings.drawTrack,
onchange : v => { settings.drawTrack=v; require("Storage").writeJSON("openstmap.json",settings); }
},
/*LANG*/"Center Map": () =>{
m.lat = m.map.lat;
m.lon = m.map.lon;
m.scale = m.map.scale;
showMap();
},
/*LANG*/"Benchmark": () =>{
m.lat = 50.001;
m.lon = 14.759;
m.scale = 2;
g.reset().clearRect(R);
redraw(18);
print("Benchmark done (31 sec)");
}
};
if (fix.fix) menu[/*LANG*/"Center GPS"]=() =>{
m.lat = fix.lat;
m.lon = fix.lon;
showMap();
};
E.showMenu(menu);
}});
}
var gjson = null;
function readTarFile(tar, f) {
const st = require('Storage');
json_off = st.read(tar, 0, 16) * 1;
if (isNaN(json_off)) {
print("Don't have archive", tar);
return undefined;
}
while (1) {
json_len = st.read(tar, json_off, 6) * 1;
if (json_len == -1)
break;
json_off += 6;
json = st.read(tar, json_off, json_len);
//print("Have directory, ", json.length, "bytes");
//print(json);
files = JSON.parse(json);
//print(files);
rec = files[f];
if (rec)
return st.read(tar, rec.st, rec.si);
json_off += json_len;
}
return undefined;
}
function loadVector(name) {
var t1 = getTime();
print(".. Read", name);
//s = require("Storage").read(name);
var s = readTarFile("delme.mtar", name);
if (s == undefined) {
print("Don't have file", name);
return null;
}
var r = JSON.parse(s);
print(".... Read and parse took ", getTime()-t1);
return r;
}
function drawPoint(a) {
lon = a.geometry.coordinates[0];
lat = a.geometry.coordinates[1];
var p = m.latLonToXY(lat, lon);
var sz = 2;
if (a.properties["marker-color"]) {
g.setColor(a.properties["marker-color"]);
}
if (a.properties.marker_size == "small")
sz = 1;
if (a.properties.marker_size == "large")
sz = 4;
g.fillRect(p.x-sz, p.y-sz, p.x+sz, p.y+sz);
g.setColor(0,0,0);
g.setFont("Vector", 18).setFontAlign(-1,-1);
g.drawString(a.properties.name, p.x, p.y);
points ++;
}
function drawLine(a, qual) {
lon = a.geometry.coordinates[0][0];
lat = a.geometry.coordinates[0][1];
i = 1;
step = 1;
len = a.geometry.coordinates.length;
step = step * qual;
var p1 = m.latLonToXY(lat, lon);
if (a.properties.stroke) {
g.setColor(a.properties.stroke);
}
while (i < len) {
lon = a.geometry.coordinates[i][0];
lat = a.geometry.coordinates[i][1];
var p2 = m.latLonToXY(lat, lon);
//print(p1.x, p1.y, p2.x, p2.y);
g.drawLine(p1.x, p1.y, p2.x, p2.y);
if (i == len-1)
break;
i = i + step;
if (i>len)
i = len-1;
points ++;
p1 = p2;
g.flip();
}
}
function drawVector(gjson, qual) {
var d = gjson;
points = 0;
var t1 = getTime();
for (var a of d.features) {
if (a.type != "Feature")
print("Expecting feature");
g.setColor(0,0,0);
// marker-size, marker-color, stroke
if (qual < 32 && a.geometry.type == "Point")
drawPoint(a);
if (qual < 8 && a.geometry.type == "LineString")
drawLine(a, qual);
}
print("....", points, "painted in", getTime()-t1, "sec");
}
function fname(lon, lat, zoom) {
var bbox = [lon, lat, lon, lat];
var r = xyz(bbox, 13, false, "WGS84");
//console.log('fname', r);
return 'z'+zoom+'-'+r.minX+'-'+r.minY+'.json';
}
function fnames(zoom) {
var bb = [m.lon, m.lat, m.lon, m.lat];
var r = xyz(bb, zoom, false, "WGS84");
while (1) {
var bb2 = bbox(r.minX, r.minY, zoom, false, "WGS84");
var os = m.latLonToXY(bb2[3], bb2[0]);
if (os.x >= 0)
r.minX -= 1;
else if (os.y >= 0)
r.minY -= 1;
else break;
}
while (1) {
var bb2 = bbox(r.maxX, r.maxY, zoom, false, "WGS84");
var os = m.latLonToXY(bb2[1], bb2[2]);
if (os.x <= g.getWidth())
r.maxX += 1;
else if (os.y <= g.getHeight())
r.maxY += 1;
else break;
}
print(".. paint range", r);
return r;
}
function log2(x) { return Math.log(x) / Math.log(2); }
function getZoom(qual) {
var z = 16-Math.round(log2(m.scale));
z += qual;
z -= 0;
if (z < meta.min_zoom)
return meta.min_zoom;
if (z > meta.max_zoom)
return meta.max_zoom;
return z;
}
function drawDebug(text, perc) {
g.setClipRect(0,0,R.x2,R.y);
g.reset();
g.setColor(1,1,1).fillRect(0,0,R.x2,R.y);
g.setColor(1,0,0).fillRect(0,0,R.x2*perc,R.y);
g.setColor(0,0,0).setFont("Vector",15);
g.setFontAlign(0,0)
.drawString(text,80,10);
g.setClipRect(R.x,R.y,R.x2,R.y2);
g.flip();
}
function drawAll(qual) {
var zoom = getZoom(qual);
var t1 = getTime();
drawDebug("Zoom "+zoom, 0);
print("Draw all", m.scale, "->", zoom, "q", qual, "at", m.lat, m.lon);
var r = fnames(zoom);
var tiles = (r.maxY-r.minY+1) * (r.maxY-r.minY+1);
var num = 0;
drawDebug("Zoom "+zoom+" tiles "+tiles, 0);
for (y=r.minY; y<=r.maxY; y++) {
for (x=r.minX; x<=r.maxX; x++) {
for (cnt=0; cnt<1000; cnt++) {
var n ='z'+zoom+'-'+x+'-'+y+'-'+cnt+'.json';
var gjson = loadVector(n);
if (!gjson) break;
drawVector(gjson, 1);
}
num++;
drawDebug("Zoom "+zoom+" tiles "+num+"/"+tiles, num/tiles);
}
}
g.flip();
Bangle.drawWidgets();
print("Load and paint in", getTime()-t1, "sec");
}
function initVector() {
var s = readTarFile("delme.mtar", "meta.json");
meta = JSON.parse(s);
}
function introScreen() {
g.reset().clearRect(R);
g.setColor(0,0,0).setFont("Vector",25);
g.setFontAlign(0,0);
g.drawString("SpaceWeaver", 85,35);
g.setColor(0,0,0).setFont("Vector",18);
g.drawString("Vector maps", 85,55);
g.drawString("Zoom "+meta.min_zoom+".."+meta.max_zoom, 85,75);
}
m.scale = 76;
m.lat = 50.001;
m.lon = 14.759;
initVector();
introScreen();
emptyMap();

BIN
apps/spacew/app.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

13
apps/spacew/metadata.json Normal file
View File

@ -0,0 +1,13 @@
{ "id": "spacew",
"name": "Space Weaver",
"version":"0.01",
"description": "Application for displaying vector maps",
"icon": "app.png",
"readme": "README.md",
"supports" : ["BANGLEJS2"],
"tags": "outdoors,gps,osm",
"storage": [
{"name":"spacew.app.js","url":"app.js"},
{"name":"spacew.img","url":"app-icon.js","evaluate":true}
]
}

82
apps/spacew/prep/minitar.js Executable file
View File

@ -0,0 +1,82 @@
#!/usr/bin/nodejs
var pc = 1;
var hack = 0;
const hs = require('./heatshrink.js');
if (pc) {
fs = require('fs');
var print=console.log;
} else {
}
print("hello world");
function writeDir(json) {
json_str = JSON.stringify(json, "", " ");
dirent = '' + json_str.length;
while (dirent.length < 6)
dirent = dirent + ' ';
return dirent + json_str;
}
function writeTar(tar, dir) {
var h_len = 16;
var cur = h_len;
files = fs.readdirSync(dir);
data = '';
var directory = '';
var json = {};
for (f of files) {
d = fs.readFileSync(dir+f);
cs = d;
//cs = String.fromCharCode.apply(null, hs.compress(d))
print("Processing", f, cur, d.length, cs.length);
//if (d.length == 42) continue;
data = data + cs;
var f_rec = {};
f_rec.st = cur;
var len = d.length;
f_rec.si = len;
cur = cur + len;
json[f] = f_rec;
json_str = JSON.stringify(json, "", " ");
if (json_str.length < 16000)
continue;
directory += writeDir(json);
json = {};
}
directory += writeDir(json);
directory += '-1 ';
size = cur;
header = '' + size;
while (header.length < h_len) {
header = header+' ';
}
if (!hack)
fs.writeFileSync(tar, header+data+directory);
else
fs.writeFileSync(tar, directory);
}
function readTarFile(tar, f) {
const st = require('Storage');
json_off = st.read(tar, 0, 16) * 1;
print(json_off);
json = st.read(tar, json_off, -1);
//print(json);
files = JSON.parse(json);
//print(files);
rec = files[f];
return st.read(tar, rec.st, rec.si);
}
if (pc)
writeTar("delme.mtaz", "delme/");
else {
print(readTarFile("delme.mtar", "ahoj"));
print(readTarFile("delme.mtar", "nazdar"));
}

View File

@ -0,0 +1,18 @@
{
"attributes": {
"type": false,
"id": false,
"version": false,
"changeset": false,
"timestamp": false,
"uid": false,
"user": false,
"way_nodes": false,
},
"format_options": {
},
"linear_tags": true,
"area_tags": false,
"exclude_tags": [],
"include_tags": [ "place", "name", "landuse", "highway" ]
}

18
apps/spacew/prep/prepare.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
if [ ".$1" == "-f" ]; then
I=/data/gis/osm/dumps/czech_republic-2023-07-24.osm.pbf
#I=/data/gis/osm/dumps/zernovka.osm.bz2
O=cr.geojson
rm delme.pbf $O
time osmium extract $I --bbox 14.7,49.9,14.8,50.1 -f pbf -o delme.pbf
time osmium export delme.pbf -c prepare.json -o $O
echo "Converting to ascii"
time cstocs utf8 ascii cr.geojson > cr_ascii.geojson
mv -f cr_ascii.geojson delme.json
fi
rm -r delme/; mkdir delme
./split.js
./minitar.js
ls -lS delme/*.json | head -20
cat delme/* | wc -c
ls -l delme.mtar

177
apps/spacew/prep/split.js Executable file
View File

@ -0,0 +1,177 @@
#!/usr/bin/nodejs --max-old-space-size=5500
// npm install geojson-vt
// docs: https://github.com/mapbox/geojson-vt
// output format: https://github.com/mapbox/vector-tile-spec/
const fs = require('fs');
const sphm = require('./sphericalmercator.js');
var split = require('geojson-vt')
// delme.json needs to be real file, symlink to geojson will not work
console.log("Loading json");
var gjs = require("./delme.json");
function tileToLatLon(x, y, z, x_, y_) {
var [ w, s, e, n ] = merc.bbox(x, y, z);
var lon = (e - w) * (x_ / 4096) + w;
var lat = (n - s) * (1-(y_ / 4096)) + s;
//console.log("to ", lon, lat);
return [ lon, lat ];
}
function convGeom(tile, geom) {
var g = [];
for (i = 0; i< geom.length; i++) {
var x = geom[i][0];
var y = geom[i][1];
//console.log("Geometry: ", geom, geom.length, "X,y", x, y);
var pos = tileToLatLon(tile.x, tile.y, tile.z, x, y);
g.push(pos);
}
return g;
}
function zoomPoint(tags) {
var z = 99;
if (tags.place == "city") z = 4;
if (tags.place == "town") z = 8;
if (tags.place == "village") z = 10;
return z;
}
function paintPoint(tags) {
var p = {};
if (tags.place == "village") p["marker-color"] = "#ff0000";
return p;
}
function zoomWay(tags) {
var z = 99;
if (tags.highway == "motorway") z = 7;
if (tags.highway == "primary") z = 9;
if (tags.highway == "secondary") z = 13;
if (tags.highway == "tertiary") z = 14;
if (tags.highway == "unclassified") z = 16;
if (tags.highway == "residential") z = 17;
if (tags.highway == "track") z = 17;
if (tags.highway == "path") z = 17;
if (tags.highway == "footway") z = 17;
return z;
}
function paintWay(tags) {
var p = {};
if (tags.highway == "motorway" || tags.highway == "primary") /* ok */;
if (tags.highway == "secondary" || tags.highway == "tertiary") p.stroke = "#0000ff";
if (tags.highway == "tertiary" || tags.highway == "unclassified" || tags.highway == "residential") p.stroke = "#00ff00";
if (tags.highway == "track") p.stroke = "#ff0000";
if (tags.highway == "path" || tags.highway == "footway") p.stroke = "#800000";
return p;
}
function writeFeatures(name, feat)
{
var n = {};
n.type = "FeatureCollection";
n.features = feat;
fs.writeFile(name+'.json', JSON.stringify(n), on_error);
}
function toGjson(name, d, tile) {
var cnt = 0;
var feat = [];
for (var a of d) {
var f = {};
var zoom = 99;
var p = {};
f.properties = a.tags;
f.type = "Feature";
f.geometry = {};
if (a.type == 1) {
f.geometry.type = "Point";
f.geometry.coordinates = convGeom(tile, a.geometry)[0];
zoom = zoomPoint(a.tags);
p = paintPoint(a.tags);
} else if (a.type == 2) {
f.geometry.type = "LineString";
f.geometry.coordinates = convGeom(tile, a.geometry[0]);
zoom = zoomWay(a.tags);
p = paintWay(a.tags);
} else {
//console.log("Unknown type", a.type);
}
//zoom -= 4; // Produces way nicer map, at expense of space.
if (tile.z < zoom)
continue;
f.properties = Object.assign({}, f.properties, p);
feat.push(f);
var s = JSON.stringify(feat);
if (s.length > 6000) {
console.log("tile too big, splitting", cnt);
writeFeatures(name+'-'+cnt++, feat);
feat = [];
}
}
writeFeatures(name+'-'+cnt, feat);
return n;
}
function writeTile(name, d, tile) {
toGjson(name, d, tile)
}
// By default, precomputes up to z30
var merc = new sphm({
size: 256,
antimeridian: true
});
//console.log(merc.ll([124, 123], 15));
//console.log(merc.px([17734, 11102], 15));
//console.log(merc.bbox(17734, 11102, 15));
//return;
console.log("Splitting data");
var meta = {}
meta.min_zoom = 0;
meta.max_zoom = 17; // HERE
// = 16 ... split3 takes > 30 minutes
// = 13 ... 2 minutes
var index = split(gjs, Object.assign({
maxZoom: meta.max_zoom,
indexMaxZoom: meta.max_zoom,
indexMaxPoints: 0,
tolerance: 30,
}), {});
console.log("Producing output");
var output = {};
function on_error(e) {
if (e) { console.log(e); }
}
var num = 0;
for (const id in index.tiles) {
const tile = index.tiles[id];
const z = tile.z;
console.log(num++, ":", tile.x, tile.y, z);
var d = index.getTile(z, tile.x, tile.y).features;
//console.log(d);
var n = `delme/z${z}-${tile.x}-${tile.y}` ;
//output[n] = d;
//console.log(n);
writeTile(n, d, tile)
}
fs.writeFile('delme/meta.json', JSON.stringify(meta), on_error);

22
apps/spacew/prep/stats.sh Executable file
View File

@ -0,0 +1,22 @@
#!/bin/bash
zoom() {
echo "Zoom $1"
cat delme/z$1-* | wc -c
echo "M..k..."
}
echo "Total data"
cat delme/* | wc -c
echo "M..k..."
zoom 18
zoom 17
zoom 16
zoom 15
zoom 14
zoom 13
zoom 12
zoom 11
zoom 10
echo "Zoom 1..9"
cat delme/z?-* | wc -c
echo "M..k..."