mirror of https://github.com/espruino/BangleApps
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
parent
f67d9eb85f
commit
3158d945aa
|
@ -0,0 +1,43 @@
|
|||
# Space Weaver 
|
||||
|
||||
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.
|
|
@ -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="))
|
||||
|
|
@ -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();
|
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
|
@ -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}
|
||||
]
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
|
|
@ -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" ]
|
||||
}
|
|
@ -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
|
|
@ -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);
|
|
@ -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..."
|
Loading…
Reference in New Issue