Skip to content

Markers positioned incorrectly: latLongToPixels uses a broken Mercator approximation #41

Description

@polina-c

Summary

MapAttributes.latLongToPixels positions markers incorrectly on the world Mercator map (SMapWorld.instructionsMercator). Markers in the northern hemisphere are pushed far above the map and end up off-screen, so in practice only southern-hemisphere markers land anywhere near their real location.

Cause

The world map is a standard spherical Mercator projection, where the vertical position of a latitude is non-linear:

y = ln(tan(π/4 + φ/2))

But latLongToPixels computes y with an ad-hoc secant-based "scaleFactor" instead (and the code itself carries a TODO admitting it is inaccurate):

double scaleFactor = secant(Angle(deg: amountAboveBaseLine));
scaleFactor = 1 - scaleFactor;
scaleFactor = scaleFactor + 1;          // = 2 - sec(Δφ)
double relativeY =
    ((topLeft.latitude - position.latitude) / mapHeightInGeoUnits) * scaleFactor;

As latitude increases, sec(Δφ) grows quickly, so scaleFactor becomes small and then negative, which throws northern points well above y = 0 (off the top of the map). The longitude → x mapping is fine; only the latitude → y mapping is broken.

There are also two leftover print(scaleFactor); statements in this method that spam stdout on every layout pass.

Reproduction

Render SimpleMap(instructions: SMapWorld.instructionsMercator, markers: [...]) with a marker at, say, London (51.5, -0.13) and one at Sydney (-33.9, 151.2). Sydney lands roughly right; London is placed above the visible map.

A note on the map bounds

The map metadata declares "g": {"a": "-180.0,90.0", "b": "180.0,-60.0"}. Those latitude bounds can't be literal for a Mercator projection — 90° maps to infinity. Calibrating the proper Mercator formula against the package's own country paths, the actual rendered vertical extent of this SVG is about 83.6°N at the top and -55.2°S at the bottom (mean error ~3px on the 651px-tall map). It would help a lot if the published bounds matched the real projection extent.

Suggested fix

Use the real Mercator transform for y, normalized between the map's true top/bottom latitudes, and drop the prints:

double _mercatorY(double latDeg) =>
    log(tan(pi / 4 + (latDeg * pi / 180) / 2));

Size latLongToPixels(LatLong position) {
  final double relativeX =
      (position.longitude - topLeft.longitude) /
          (bottomRight.longitude - topLeft.longitude);

  final double top = _mercatorY(topLeft.latitude);
  final double bottom = _mercatorY(bottomRight.latitude);
  final double relativeY =
      (top - _mercatorY(position.latitude)) / (top - bottom);

  return Size(relativeX * mapWidth, relativeY * mapHeight);
}

(For this to be exact, topLeft.latitude / bottomRight.latitude in the world map metadata need to reflect the real ~83.6 / -55.2 extent rather than 90 / -60.)

Thanks for the package!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions