Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
434533f
Add road-following routing to the maps API (#5478)
shai-almog Jul 27, 2026
ad93d43
Address review feedback on the routing API
shai-almog Jul 27, 2026
d7b4d09
Drop the unsound completion backstop from the routing request
shai-almog Jul 27, 2026
e6d5d9b
Raise IOException for malformed route entries instead of an unchecked…
shai-almog Jul 27, 2026
514b886
Tighten routing error reporting, precision handling and OSRM mode docs
shai-almog Jul 27, 2026
dfd37f2
Drop the javadoc-only Polyline import from Routing
shai-almog Jul 27, 2026
0f39d04
Harden the routing contracts against null callbacks and malformed input
shai-almog Jul 27, 2026
4c28e4d
Frame bounds on native maps and guarantee routing callback delivery
shai-almog Jul 27, 2026
83811d9
Report queue rejections through the callback; document the bounds wra…
shai-almog Jul 27, 2026
89a5ec0
Center fitted bounds at the Mercator midpoint; reject empty route geo…
shai-almog Jul 28, 2026
8f9bac6
Clamp latitudes to the Mercator limit and detach the progress listener
shai-almog Jul 28, 2026
b8ff6f8
Reject NaN coordinates and guard the whole service interaction
shai-almog Jul 28, 2026
9469248
Name roundabout exits and make the callback wrapper thread-safe
shai-almog Jul 28, 2026
b3f865e
Handle roundabout turns, strict legs/steps, and degenerate fit bounds
shai-almog Jul 28, 2026
8742ec0
Fit Apple bounds in the provider's zoom model; claim delivery before …
shai-almog Jul 28, 2026
ffa69e6
Add the standard license header to the Apple map provider template
shai-almog Jul 28, 2026
2ac451c
Reject non-Route results and read MapKit state on the main thread
shai-almog Jul 28, 2026
93b3f0f
Read the progress dispatcher into a local before firing
shai-almog Jul 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions CodenameOne/src/com/codename1/io/NetworkManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -1096,8 +1096,10 @@ private boolean runCurrentRequest(@Async.Execute ConnectionRequest req) {
break;
}

if (progressListeners != null) {
progressListeners.fireActionEvent(new NetworkEvent(req, NetworkEvent.PROGRESS_TYPE_INITIALIZING));
// progressListeners might be made null by a separate thread
EventDispatcher initListeners = progressListeners;
if (initListeners != null) {
initListeners.fireActionEvent(new NetworkEvent(req, NetworkEvent.PROGRESS_TYPE_INITIALIZING));
}
if (req.getShowOnInit() != null) {
req.getShowOnInit().showModeless();
Expand Down Expand Up @@ -1130,8 +1132,15 @@ private boolean runCurrentRequest(@Async.Execute ConnectionRequest req) {
if (requestWasCompleted) {
req.complete = true;
}
if (progressListeners != null) {
progressListeners.fireActionEvent(new NetworkEvent(req, NetworkEvent.PROGRESS_TYPE_COMPLETED));
// Read once into a local. A listener removed from the EDT --
// which is where postResponse() runs, queued while this thread
// was still finishing the request -- can null the field between
// the check and the dispatch, and the NullPointerException that
// followed would escape NetworkThread.run() and kill the only
// network worker. Same reasoning as fireProgressEvent.
EventDispatcher completionListeners = progressListeners;
if (completionListeners != null) {
completionListeners.fireActionEvent(new NetworkEvent(req, NetworkEvent.PROGRESS_TYPE_COMPLETED));
}
if (req.getDisposeOnCompletion() != null && !req.isRedirecting()) {
// there may be a race condition where the dialog hasn't yet appeared but the
Expand Down
2 changes: 1 addition & 1 deletion CodenameOne/src/com/codename1/maps/MapView.java
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public void run() {
/// screen it must cover proportionally more physical pixels (the standard
/// slippy-map convention: mdpi = 1, hdpi = 1.5, xhdpi = 2, xxhdpi = 3, ...)
/// otherwise the viewport spans many tiles and the map shows too much area.
private static double devicePixelRatio() {
static double devicePixelRatio() {
switch (Display.getInstance().getDeviceDensity()) {
case Display.DENSITY_HIGH:
return 1.5;
Expand Down
76 changes: 73 additions & 3 deletions CodenameOne/src/com/codename1/maps/NativeMap.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import com.codename1.maps.spi.MapProviderRegistry;
import com.codename1.maps.vector.MapStyle;
import com.codename1.maps.vector.TileSource;
import com.codename1.maps.vector.WebMercator;
import com.codename1.util.MathUtil;
import com.codename1.ui.CN;
import com.codename1.ui.Component;
import com.codename1.ui.Container;
Expand Down Expand Up @@ -335,9 +337,77 @@ public void fitBounds(MapBounds bounds, int paddingPixels) {
fallback.fitBounds(bounds, paddingPixels);
return;
}
// Native providers center on the bounds; precise fit is provider work.
provider.setCamera(mapId, bounds.getCenter().getLatitude(),
bounds.getCenter().getLongitude(), provider.getZoom(mapId), 0, 0);
if (bounds == null) {
return;
}
double zoom = zoomToFit(bounds, getWidth(), getHeight(), paddingPixels,
MapView.devicePixelRatio());
Comment thread
shai-almog marked this conversation as resolved.
if (Double.isNaN(zoom)) {
// Nothing to fit to yet (no layout, or a single point): keep the
// zoom and just recenter.
zoom = provider.getZoom(mapId);
} else {
zoom = Math.max(provider.getMinZoom(mapId),
Math.min(provider.getMaxZoom(mapId), zoom));
}
// Center on the projected midpoint, not the arithmetic one: Mercator
// stretches away from the equator, so a tall band centered on the mean
// of its latitudes hangs off the top of the viewport the fit just
// sized for it.
provider.setCamera(mapId,
WebMercator.centerLatitude(bounds.getSouthWest().getLatitude(),
bounds.getNorthEast().getLatitude()),
bounds.getCenter().getLongitude(), (float) zoom, 0, 0);
}

/// The zoom at which `bounds` fills a `viewWidth` x `viewHeight` viewport
/// of device pixels, inset by `paddingPixels` on every edge, or
/// `Double.NaN` when there is nothing to fit to -- no layout yet, or bounds
/// with no extent.
///
/// `fitBounds` used to keep the current zoom and merely recenter, so a
/// region larger than the viewport stayed clipped and
/// [MapSurface#fitBounds] did not mean the same thing on a native map as
/// on a vector one. This solves for the zoom the way the vector engine
/// does, in the same Web Mercator units, so both surfaces frame a region
/// identically. Package visible so the arithmetic can be tested without a
/// native provider.
static double zoomToFit(MapBounds bounds, int viewWidth, int viewHeight,
int paddingPixels, double pixelRatio) {
if (bounds == null || viewWidth <= 0 || viewHeight <= 0 || pixelRatio <= 0) {
return Double.NaN;
}
if (viewWidth - 2 * paddingPixels <= 0 || viewHeight - 2 * paddingPixels <= 0) {
// Padding has swallowed the viewport: there is no room left to fit
// anything into, and pretending it is one pixel wide would solve
// for an absurd zoom.
return Double.NaN;
}
// Native map SDKs use the slippy convention, where a zoom level spans
// 256 logical points; the component's size is in device pixels, so it
// converts through the same density ratio the vector engine uses.
double usableWidth = (viewWidth - 2 * paddingPixels) / pixelRatio;
double usableHeight = (viewHeight - 2 * paddingPixels) / pixelRatio;
double worldWidth = Math.abs(
WebMercator.lonToWorldX(bounds.getNorthEast().getLongitude(), 0)
- WebMercator.lonToWorldX(bounds.getSouthWest().getLongitude(), 0));
// Clamped, or a bound touching a pole projects to infinity and the
// solved zoom collapses.
double worldHeight = Math.abs(
WebMercator.latToWorldY(
WebMercator.clampLatitude(bounds.getNorthEast().getLatitude()), 0)
- WebMercator.latToWorldY(
WebMercator.clampLatitude(bounds.getSouthWest().getLatitude()), 0));
if (worldWidth <= 0 && worldHeight <= 0) {
return Double.NaN;
}
double horizontal = worldWidth <= 0 ? Double.MAX_VALUE : log2(usableWidth / worldWidth);
double vertical = worldHeight <= 0 ? Double.MAX_VALUE : log2(usableHeight / worldHeight);
return Math.min(horizontal, vertical);
}

private static double log2(double v) {
return MathUtil.log(v) / MathUtil.log(2);
}

// ---- MapSurface: map objects -----------------------------------------
Expand Down
31 changes: 31 additions & 0 deletions CodenameOne/src/com/codename1/maps/Polyline.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@

/// A connected sequence of line segments drawn on a map. Add one through
/// [MapSurface#addPolyline(Polyline)].
///
/// A polyline joins the vertices you give it with *straight* segments; it
/// knows nothing about roads. To draw a route that follows the road network,
/// ask a routing service for the road geometry and draw that -- see
/// [com.codename1.maps.routing.Routing], or decode a geometry you fetched
/// yourself with [#fromEncoded(String)].
public final class Polyline extends MapObject {

private final List points;
Expand All @@ -50,6 +56,31 @@ public Polyline(LatLng[] pts) {
}
}

/// Creates a polyline from an *encoded polyline* geometry at
/// [PolylineCodec]'s default precision -- the shape virtually every
/// directions API returns for a route.
public static Polyline fromEncoded(String encoded) {
return through(PolylineCodec.decode(encoded));
}
Comment thread
shai-almog marked this conversation as resolved.
Comment thread
shai-almog marked this conversation as resolved.

/// Creates a polyline from an *encoded polyline* geometry at an explicit
/// decimal precision (5 for the classic format, 6 for `polyline6`).
///
/// Throws `IllegalArgumentException` when `precision` falls outside 1 to
/// 10; a precision that cannot scale coordinates sensibly would otherwise
/// decode into a line drawn in the wrong place. See [PolylineCodec].
public static Polyline fromEncoded(String encoded, int precision) {
return through(PolylineCodec.decode(encoded, precision));
}

/// Wraps already-decoded vertices, so neither factory has to restate the
/// codec's default precision.
private static Polyline through(List decoded) {
Polyline pl = new Polyline();
pl.points.addAll(decoded);
return pl;
}
Comment thread
shai-almog marked this conversation as resolved.

/// Appends a vertex.
public Polyline addPoint(LatLng point) {
points.add(point);
Expand Down
Loading
Loading