App 2.39.0, server 2.7.0: Grid with map
A new Overview style: the Grid over the roads round the car from
OpenStreetMap, drawn in the floor's perspective, heading up, the bigger
roads glowing brighter, the car as a chevron.
Server: POST /v1/map/roads {z:14,x,y} fetches a tile's drivable roads
from Overpass (OVERPASS_URL; one request at a time, at least a second
apart), simplifies them to tile-local points in four classes, caches
them for 30 days and never logs the tile.
App: MapRoads keeps the 3x3 tiles round the car from its own cache or
the server; GPS only while the style is shown; bearing from the GPS or
from successive positions. Without GPS, server or roads the plain floor
says why. OSM attribution on screen and in LICENSE.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LhzAVaUyqcw5PkGjz4mWW6
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
package com.evsuite.abrp;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONObject;
|
||||
|
||||
/**
|
||||
* The roads of one map tile as the server sends them (server/maps.go): per road its class
|
||||
* (0 motorway and trunk, 1 primary and secondary, 2 tertiary and unclassified, 3 residential)
|
||||
* and its points in tile units, plus each road's bounding box for skipping far ones quickly.
|
||||
* {@link #parse} fails closed: anything unexpected is no tile at all. Pure Java, unit-tested.
|
||||
*/
|
||||
final class RoadTile {
|
||||
|
||||
static final int CLASSES = 4;
|
||||
/** Points may lie a little beyond the tile (a road is kept to its first point outside). */
|
||||
private static final int SLACK = MapGeometry.EXTENT;
|
||||
private static final int MAX_ROADS = 20_000;
|
||||
|
||||
final int x, y;
|
||||
final int[] classes;
|
||||
/** Per road: x0, y0, x1, y1, … in tile units. */
|
||||
final int[][] points;
|
||||
/** Per road: minX, minY, maxX, maxY. */
|
||||
final int[][] bounds;
|
||||
|
||||
private RoadTile(int x, int y, int[] classes, int[][] points, int[][] bounds) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.classes = classes;
|
||||
this.points = points;
|
||||
this.bounds = bounds;
|
||||
}
|
||||
|
||||
/** The tile in {@code json}, which must be the one asked for; null when it is not usable. */
|
||||
static RoadTile parse(String json, int expectX, int expectY) {
|
||||
try {
|
||||
JSONObject o = new JSONObject(json);
|
||||
if (o.getInt("z") != MapGeometry.ZOOM || o.getInt("x") != expectX || o.getInt("y") != expectY
|
||||
|| o.getInt("extent") != MapGeometry.EXTENT) {
|
||||
return null;
|
||||
}
|
||||
JSONArray ways = o.getJSONArray("ways");
|
||||
if (ways.length() > MAX_ROADS) return null;
|
||||
int n = ways.length();
|
||||
int[] classes = new int[n];
|
||||
int[][] points = new int[n][];
|
||||
int[][] bounds = new int[n][];
|
||||
for (int i = 0; i < n; i++) {
|
||||
JSONObject way = ways.getJSONObject(i);
|
||||
int c = way.getInt("c");
|
||||
JSONArray p = way.getJSONArray("p");
|
||||
if (c < 0 || c >= CLASSES || p.length() < 4 || p.length() % 2 != 0) return null;
|
||||
int[] pts = new int[p.length()];
|
||||
int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE, maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE;
|
||||
for (int k = 0; k < pts.length; k++) {
|
||||
int value = p.getInt(k);
|
||||
if (value < -SLACK || value > MapGeometry.EXTENT + SLACK) return null;
|
||||
pts[k] = value;
|
||||
if (k % 2 == 0) {
|
||||
minX = Math.min(minX, value);
|
||||
maxX = Math.max(maxX, value);
|
||||
} else {
|
||||
minY = Math.min(minY, value);
|
||||
maxY = Math.max(maxY, value);
|
||||
}
|
||||
}
|
||||
classes[i] = c;
|
||||
points[i] = pts;
|
||||
bounds[i] = new int[] { minX, minY, maxX, maxY };
|
||||
}
|
||||
return new RoadTile(expectX, expectY, classes, points, bounds);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user