Skip to content

Image Maps: Pixels, bounds, coordinates and making it all work nicely #130

Moonbase59 started this conversation in Show and tell
Image Maps: Pixels, bounds, coordinates and making it all work nicely #130
Jun 21, 2021 · 3 comments · 1 reply

Now I’m not a gamer, but still wanted to know more about all these "image maps". Let me share what I’ve found out.

Map projections (CRS, Coordinate Reference System)

Real-world maps use a map projection, a way to flatten a globe's surface into a plane in order to make a map. Google maps, OpenStreetMap and Leaflet use a web variant of the Mercator projection, usually with WGS 84 coordinates.

Image maps use a "flat" projection, namely, CRS.Simple, i.e., coordinates translate directly to pixels in the image. This does not necessarily mean that "1 pixel = 1 map unit", though.

But it means that real-world and image maps are inherently incompatible, i.e. you cannot screenshot part of a map and expect to get correct coordinates when using it as an image map. (The map gotten from the screenshot has a Web Mercator projection, while an image map is "flat".)

Gaming maps

Even if they aren’t all in a flat projection, for all practical purposes we can assume so, and just use y,x coordinates as "game coordinates". Sometimes even these maps have a map scale on them, so we can exactly measure how long, say 100 "game miles" are, and adjsut for that, using the bounds and/or scale parameter in the code block.

Here are some interesting articles to read:

Real example: A map of Middle Earth

So I took this image of a Middle Earth map from the net. It’s a JPEG file, 1600x1200 pixels in size. And it has a map scale on it.

I measured the distance between the zero and the 100 miles point on the scale, and it is exactly 85 pixels. So we now know the map has 100 miles per 85 pixels. Since I want my map units to be miles, and be able to measure distances on the map, let’s calculate.

We can use either bounds to set the map bounds to (pseudo) "real" coordinates, or use scale. Scale would be easy, it’s just 1 divided by (85 (pixel) / 100 (miles)) = 1.176470588.

But this would not give us nice "mile" coordinates, which I intend. So let’s calculate the bounds.

  • For a 1600px wide image, 1600 * 1/(85/100) = 1600 * 1.176470588 = 1882.35
  • For 1200px height, 1200 * 1/(85/100) = 1200 * 1.176470588 = 1411.76

Meaning, the complete (1600x1200 px) map shows us an area of 1882.35 by 1411.76 miles (= "gaming length units").

So let’s make a map instance and give it bounds in "gaming coordinates", i.e. miles, in our case.
And to verify, let’s add some markers in "game coordinates": The center, the southwest corner, and the northeast corner. If we simply leave out the scale parameter, it’ll default to 1.

Remember that gaming coordinates will not be latitude, longitude, but y, x (Leaflet uses a reversed notation here, also valid for the bounds). Lines starting with a hash # are just comments. I recommend using them, so you’ll still know what you did and why in the future.

```leaflet
id: Middle Earth
# the image is 1600x1200 px
image: [[middle-earth-map.jpg]]
# bounds uses y,x (CRS.Simple)
# bounds in miles (as per map scale: 100 mi = 85 px)
bounds: [[0,0], [1411.76, 1882.35]]
# after this, southwest = 0,0; northeast = 1411.76, 1882.35
height: 500px
# lat/long in percent doesn’t center
# does ist not respect scale or bounds?
# would be easier if this was a coordinate in "bounds" range,
# as the "final" coordinates will be.
lat: 50
long: 50
minZoom: -2
maxZoom: 1
defaultZoom: -1
unit: miles
# make check markers in the center, SW, NE (y,x in "bounds" coordinates)
# (in the middle of Fangorn!)
marker: Current, 705.88, 941.175
# Southwest (0,0)
marker: Check, 0, 0
# Northeast (1411.76, 1882.35)
marker: Check, 1411.76, 1882.35
```

Let’s see how this looks if we zoom out a little (so we can see the edge markers):

Auswahl_030

Perfect—this looks as we expected it. Let’s verify if our coordinate system really works by setting two markers at the "0" and "100 miles" map scale and see if they show a distance of "100 miles":

Auswahl_031

We can see that the distance shown is exactly 100 miles, and the coordinates reflect that (miles instead of latitude, longitude, counting from the southwest corner of the map).

A perfect map! Try to measure the distance between Hobbiton and Minas Morgul.

Using overlays

Yes, it’s even possible to use (circle) overlays with image maps. Just use the map coordinates (which can be copied using Ctrl+Shift+Click):

overlay: [[red, [705.88, 941.175], 100 miles, "Test Overlay"]]

You’ll have to exactly follow this syntax, though, and use the map units. (The docs aren’t updated yet.) Watch out for any errors in the developer console!

Using GeoJSON

It works in principle, but there’s still a small bug that prevents display: Image Maps: Using GeoJSON doesn’t show anything.

EDIT: The bug has been fixed in 3.19.0.

EDIT FROM VALENTINE195: Here is a picture with the Forest of Fangorn GeoJSON:
image

I tried making up a GeoJSON file around the area of the Forest of Fangorn, using coordinates copied from the map.

Remember that …

  • Leaflet favours lat,lon coordinates (for CRS.Simple, this maps to y,x, an unusual sequence for Cartesian coordinates).
  • GeoJSON wants lon,lat coordinates, so if you copy coordinates from the map using Ctrl+Shift+Click, these have to be reversed for use in GeoJSON! (So 393, 142 in the map becomes 142, 393 in your GeoJSON file.)

Here is a very simple example that puts a red line on top of the 0..100 mile map scale:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "LineString",
        "coordinates": [
            [142, 393],
            [242, 393]
        ]
      },
      "properties": {
        "name": "Measuring line 0–100 miles",
        "stroke": "#ff0000",
        "stroke-width": 5,
        "stroke-opacity": 1
      }
    }
  ]
}

If anyone wants to play around, this is the GeoJSON file I used for testing: geo-fangorn.geojson.zip. It should show the area of the Forest of Fangorn.

Note

Strictly speaking, using GeoJSON here is a border case. But it’s just too nice to let it pass unmentioned!

RFC 7946 Section 4 "Coordinate Reference System" clearly states:

The coordinate reference system for all GeoJSON coordinates is a
geographic coordinate reference system, using the World Geodetic
System 1984 (WGS 84) [WGS84] datum, with longitude and latitude units
of decimal degrees. […] An OPTIONAL third-position element SHALL
be the height in meters above or below the WGS 84 reference
ellipsoid.

But it also states:

Note: the use of alternative coordinate reference systems was
specified in [GJ2008], but it has been removed from this version of
the specification because the use of different coordinate reference
systems -- especially in the manner specified in [GJ2008] -- has
proven to have interoperability issues. […] However, where all
involved parties have a prior arrangement, alternative coordinate
reference systems can be used without risk of data being
misinterpreted.

So be aware that

  • your GeoJSON won’t be able to display on anything except an image map with the exact same bounds, most probably really only on this map.
  • if ever you should change the map’s scale or bounds, all GeoJSON coordinates will have to be re-calculated! We’re using a very personal and specific "coordinate system" here, after all.
  • GeoJSON linters can probably lint it, but will fail to display it correctly (most use an underlying real-world map for this).

It can still be great fun exploring and cartographing a game or fantasy map this way!

Caveat: The unit parameter in image maps holds just a name!

You might be used to the fabulous automatic unit conversions with real-world maps. This is not so for image maps!

In image maps, specifying a unit in the code block is just a name. It could be "miles", "leagues", "kerboffles" or whatever, it will never convert map units, just give them a name.

Hint: Should you really need something like "kilometres" instead of "miles" for this kind of map, adjust the bounds! (But this will render already defined markers useless, so decide before.)

Can I use "fantasy coordinates" in other notes, to populate the map?

Absolutely. Just be sure you don’t mix them up with "normal", real-world coordinates. And be sure not to forget that you will have to recalculate all these if ever changing the map’s scale or bounds.

Here’s an example for the Elvenking’s Halls:

# this location is in "Middle Earth Map" coordinates!
location: [1142.0469, 1208]
mapmarker: Friends
tags: [middleearth]

Hint: Coordinates can easily be copied from the map by zooming in and using Ctrl+Shift+Click. This will copy the (map) location into the clipboard and you can paste it into the corresponding note’s YAML header.

I recommend using a special tag, or put all files for such a map project in one folder, so you can easily distinguish them. Say, you used a special folder for all Middle Earth files, namely Locations/Middle Earth. You could then collect all notes with interesting map information like this in your map:

markerFolder: Locations/Middle Earth

They would then show up on your map in the correct place:

middle-earth-map-marker-from-note

A minor bug — Centering the map

EDIT: This bug has been fixed in 3.19.0.

We defined a marker in the center of the map, and upon initial view, our center marker is shown in the exact center. (Beware, it’s in the middle of Fangorn Forest!)

Auswahl_032

Only when we try to "Reset view" (Circles button), the map shifts upwards.

Auswahl_033

Switching on verbose mode (verbose: true) tells us that it’s centering wrongly:

Obsidian Leaflet Map Middle Earth: Resetting map view to [0, 941.175].

It should be resetting to 705.88, 941.175. See Image maps: Initial view ok, "reset view" shifts map upwards.

Conclusion

I hope this gave you some insight (it certainly did for me!), thanks to @valentine195 for making it, and happy mapping!


EDIT: Added 2021-08-06 by @Moonbase59:

Dark mode and CSS filtering

Maps can have a Dark Mode, use

darkMode: true

It applies this CSS filter, which can easily be overriden by a CSS snippet:

.block-language-leaflet .leaflet-container .dark-mode {
    filter: brightness(0.6) invert(1) contrast(3) hue-rotate(200deg)
        saturate(0.3) brightness(0.7);
}

Replies

3 comments
·
1 reply

Great, you're great!

1 reply
@Moonbase59

Thanks! ;-)

Updated original post re fixed "map shifting" and "GeoJSON display" in 3.19.0.

0 replies

Thanks a lot for all those clear explanations! I have a question about the area of Fangorn you did, importing a GeoJSON file. I'm using a different map (Hyrule 😀) and want to create an area of a region of the map, like you did. How can i create this GeoJSON file like you did, with copy and paste from the map coordinates? Can't i just write the area in the source block by any chance?

0 replies
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
3 participants