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
77 lines
3.1 KiB
Java
77 lines
3.1 KiB
Java
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;
|
|
}
|
|
}
|
|
}
|