Skip to content

Lesson 4 — Coordinates and the lat/lng convention

This is a short lesson about a specific footgun: the order of coordinates when you write them in code. Almost every developer working with maps loses an hour to this at some point. It’s worth spending 15 minutes here so you don’t.

Two conventions exist, both legitimate, and they don’t agree. Different tools, libraries, and data sources pick one or the other, and there’s no consensus. The Vera gem makes a specific choice — accept the Ruby-natural one, store the MapLibre-native one — and the reasons matter.

Two conventions, both reasonable

When you write a coordinate in everyday speech, you almost always say latitude first, longitude second. Sydney is at latitude -33.87, longitude 151.21. Maps in atlases label latitudes on the side and longitudes on the top. Search engines expect “lat, lng” as the input format. GPS coordinates from a phone are formatted lat-then-lng. Most humans encounter coordinates as “latitude, longitude.”

But there’s another tradition. Mathematics and graphics conventions put horizontal coordinate first, vertical coordinate second — that’s (x, y) from primary school geometry, mirrored in computer graphics where the X axis is horizontal and the Y axis is vertical.

Mapped onto Earth, longitude is the horizontal coordinate (lines running north-south, varying east-west) and latitude is the vertical coordinate (lines running east-west, varying north-south). So the mathematical convention says longitude first, latitude second.

Both conventions are defensible. The first is more human-readable; the second is more mathematically consistent.

How different tools pick

A rough survey:

  • Geography textbooks, search engines, address inputs: latitude first.
  • GPS devices and phone location APIs: latitude first.
  • PostGIS’s ST_Point() function (and most spatial SQL): longitude first.
  • GeoJSON, the standard format for spatial data on the web: longitude first. (The GeoJSON spec is explicit: [longitude, latitude], and it cites the mathematical convention.)
  • Leaflet: latitude first. ([lat, lng] — Leaflet picked the human-readable convention deliberately.)
  • MapLibre and Mapbox: longitude first. ([lng, lat] — these libraries picked the GeoJSON-aligned convention because most data they consume is GeoJSON.)

So the libraries we’re using have landed on different conventions — Leaflet on lat-first, MapLibre on lng-first. This is one reason migrating from Leaflet to MapLibre is painful: every coordinate in your code has to be flipped.

Vera’s choice

The Vera gem accepts [lat, lng] in its DSL — the Ruby-natural convention. Internally, it stores [lng, lat] because that’s what MapLibre needs and what GeoJSON specifies.

Why the asymmetry?

  • The DSL is what the developer writes. Reading and writing [lat, lng] matches the way coordinates appear in databases, search results, GPS readouts, and conversation. Forcing [lng, lat] in the DSL would create a constant translation burden: copy-paste a coordinate from anywhere, mentally swap, type it. Errors compound.
  • The serialised configuration is what MapLibre consumes. MapLibre expects [lng, lat] and won’t accept anything else. GeoJSON, which the gem also produces and consumes, is similarly fixed at [lng, lat].

The gem hides the boundary. You write Ruby coordinates the human way; they reach the browser the GeoJSON way. The translation happens at the moment of serialisation, in Vera::Coordinates.to_maplibre, and it’s the only place the flip occurs.

You can verify this from Lesson 3’s Lab. Look at the data attribute on the rendered map div:

1
data-vera--map-config-value='{"...","centre":[133.77,-25.27],...}'

The centre is [133.77, -25.27] — longitude first, latitude second. But the Ruby that produced it (in Vera::Map’s default constants) is:

1
DEFAULT_CENTRE = [-25.2744, 133.7751].freeze   # [lat, lng]

The gem’s constant is in [lat, lng] — the Ruby way. The serialised JSON is in [lng, lat] — the MapLibre way. Same information, two conventions, the gem bridges the gap.

The practical rule

When you write Ruby code for the gem, write [lat, lng].

Sydney: [-33.8688, 151.2093]. Brisbane: [-27.4698, 153.0251]. Eiffel Tower: [48.8584, 2.2945]. Always lat first, lng second.

When you’re inspecting JSON, looking at GeoJSON files, or working with raw MapLibre, the order flips: longitude first, latitude second. But in Ruby code targeting the gem, you don’t have to think about it.

That’s the rule. The rest of the lesson is about what to watch for at the boundary — when you bring coordinates in from elsewhere — and the consequences of getting it wrong.

When you copy coordinates from elsewhere

The danger zone is importing coordinates from external sources. Different sources use different conventions, and you have to know which is which before you paste them into Ruby code.

Sources that give you [lat, lng] (drop these directly into the gem’s DSL):

  • Google Maps URL parameters (the q=lat,lng form)
  • Apple Maps share links
  • Most “what are the coordinates of X?” search results
  • Wikipedia’s geographic coordinates infobox
  • GPS readouts from a phone or device
  • The latitude and longitude columns of most databases

Sources that give you [lng, lat] (you’ll need to swap):

  • GeoJSON files of any kind
  • The output of PostGIS’s ST_AsGeoJSON() (or the coordinates array from any GeoJSON feature)
  • MapLibre and Mapbox documentation
  • Most JavaScript mapping examples
  • The geometry column of a PostGIS table when extracted via ST_X() and ST_Y() (X is longitude, Y is latitude)

If a source labels its values explicitly — “lat: -33.87, lng: 151.21” or {type: "Point", coordinates: [151.21, -33.87]} — you can read off the convention. If a source just shows a pair of numbers without labels, check the documentation. Or test with a known-easy location: Sydney is around -33.87 N, 151.21 E. If a coordinate pair from your source produces [151, -33], it’s [lng, lat]. If it produces [-33, 151], it’s [lat, lng].

What goes wrong when you swap them

When you accidentally pass [lng, lat] to the gem (because you copied from GeoJSON without flipping), the gem dutifully treats the numbers as [lat, lng]. Sydney’s [151.21, -33.87] becomes a “coordinate” at latitude 151.21, longitude -33.87.

MapLibre rejects latitude values outside the [-90, 90] range outright. The map fails to initialise and you’ll see a JavaScript error in the browser’s developer console, something like:

1
2
3
4
5
Error: Invalid LngLat latitude value: must be between -90 and 90
    at new Vd (lng_lat.ts:73:19)
    at Vd.convert (lng_lat.ts:162:20)
    ...
    at t.connect (map_controller-41206b62.js:19:16)

The error is precise about what went wrong (latitude out of range), and the stack trace points at the vera--map controller’s connect call as the place the error surfaced.

This is the classic symptom: an “Invalid LngLat” error in the console, the map div empty. When you see this, check coordinate order before checking anything else.

A more pernicious case is when both numbers happen to be valid latitudes — for example, [40, 50]. Latitude 40, longitude 50 is a real place (eastern Turkey); latitude 50, longitude 40 is also real (western Russia). Reversing coordinates that are both within [-90, 90] doesn’t error; the map just shows the wrong location silently. This is rare because longitudes in inhabited parts of the world are usually beyond ±90, but it can bite for places near the prime meridian or the equator.

Activity 1 — Get it wrong on purpose

Edit your Lab page’s render call and replace the centre with reversed Sydney coordinates:

1
2
3
4
5
6
<%= render Vera::Map.new(
      id:     "lab-map",
      height: "600px",
      centre: [151.2093, -33.8688],
      zoom:   12
    ) %>

(Notice this is [lng, lat] — the wrong order for the gem’s DSL.)

Reload the page. The map area will be empty. Open the browser’s developer console and you’ll see an error like Invalid LngLat latitude value: must be between -90 and 90. The number 151.2093 is being treated as a latitude, which exceeds the valid range, and MapLibre refuses to initialise the map at all.

Now fix the order:

1
2
3
4
5
6
<%= render Vera::Map.new(
      id:     "lab-map",
      height: "600px",
      centre: [-33.8688, 151.2093],
      zoom:   12
    ) %>

Sydney appears as expected. Take a moment to internalise the difference: same numbers, different order, completely different result.

Worth remembering this error message specifically. When you see “Invalid LngLat” in your console weeks or months from now, the fix is almost always to swap the coordinate order somewhere upstream — in your view, in your seed data, in the GeoJSON your controller produces.

One more pattern

The gem also accepts [lat, lng] in markers, popup positions, and the bounding-box action helpers. Anywhere a coordinate is expressed in the gem’s DSL, it’s [lat, lng]. That’s a deliberate consistency.

You’ll see this throughout the rest of the tutorial. Markers, flyto helpers, bounding-box queries, drawn shapes — all coordinates pass through the same translation boundary, all read as [lat, lng] in Ruby and [lng, lat] after serialisation.

The one place this doesn’t hold is when you’re working directly with GeoJSON your application produces or consumes. GeoJSON is [lng, lat] by spec; if you’re hand-building a GeoJSON object in Ruby for some reason, you write [lng, lat] because that’s what GeoJSON requires. The Vera gem doesn’t translate GeoJSON contents — they pass through as-is.

In practice, hand-building GeoJSON in Ruby is rare. PostGIS produces GeoJSON; Rails serialises it; the gem accepts URLs that resolve to GeoJSON. The reader almost never writes [lng, lat] in Ruby code by hand. But knowing it’s the exception keeps you from being surprised when it comes up.

Activity 2 — Verify a coordinate’s order

Open https://www.google.com/maps in a browser. Right-click anywhere on the map. The first item in the context menu shows the coordinates of that point — for example, -27.4705, 153.0260 if you clicked on Brisbane.

Note the order: latitude first (negative for the southern hemisphere), longitude second (positive for east of Greenwich). That’s [lat, lng] — drop directly into the gem’s DSL.

Now find the same point on https://geojson.io. Click anywhere on the map. The right pane shows the GeoJSON representation:

1
2
{"type": "Feature", ..., "geometry": {"type": "Point",
 "coordinates": [153.0260, -27.4705]}}

The order is reversed: [lng, lat]. That’s GeoJSON’s convention; you’d need to flip before pasting into the gem’s DSL.

Same point, two formats, two orders.

Where this leaves us

You now know:

  • Both [lat, lng] and [lng, lat] are legitimate conventions, and different tools pick differently.
  • The gem accepts [lat, lng] in its DSL — the Ruby-natural convention.
  • Internally, the gem flips to [lng, lat] for MapLibre and GeoJSON consumption.
  • Most external sources give you [lat, lng] directly (Google Maps, GPS, databases). GeoJSON gives you [lng, lat] and needs flipping.
  • An “Invalid LngLat” error in the console — or a map showing the wrong location — is almost always a reversed-coordinates bug.

Lesson 5 adds the controls that give the Lab map the polish of a real piece of UI — navigation, scale, attribution. After that, Module 2 is done and we move into PostGIS work proper in Module 3.