1
\$\begingroup\$

I'm working on an little mapbox based game and I'm currently working on an latitude/longitude-based chunk system. The distance between each of them should be the same. As you can see in the example the width and height of each chunk are equal. The little grey square is just the chunk middle. Those chunks were created near latitude/longitude zero, where they aren't effected by the Mercator projection.

Chunks One

But due to the Mercator projection the distances between the chunks are increasing as more as they move away from the middle (they look kinda stretched).

Chunk Two

As you can see the distances between each one aren't equal anymore. The height of each of those chunks did increase. Here's another picture of the Mercator projection to visualize what I mean:

enter image description here

The chunks of the first picture were created near the middle of the word map (where the big black upper arrow points towards). The chunks from the second pictures were created near the bottom of the world map, where the other arrow points towards.

The type of game I'm developing requires equal chunk sizes and also it doesn't look that good when at some point the chunks look different due to the stretching.

I want to achieve a chunk system like this on the mercator projection :

enter image description here

I calculate the chunks as follows:

First we need the player latitude/longitude coordinates : 3.0146683, 5.3046076

One latitude is about: 110.57 km -> 110570 meters.
One longitude is about: 111.32 km -> 111320 meters.

One chunk should be about 750 meters on the map.

Because latitude and longitude got different ranges we need to adjust the chunk size.

One latitude factor = 110570 / 750 = 147,43.
One longitude factor = 111320 / 750 = 148,42.

One latitude chunk size = 110570 / 147 = 750 meters.
One longitude chunk size = 111320 / 148,42 = 750 meters.

(Maybe this step is unnecessary)

Now we convert the Lat/Lng to meters.

Latitude in Meter = 3.0146683 * 110570 meters = 333 331.873931 Meters.
Longitude in Meter = 5.3046076* 111320 meters = 590 508.918032 Meters.

Now we need to calculate the meters till the last chunk by using modulo.

Latitude Modulo => 333 331.873931 modulo 750 = 331 meter.
Longitude Modulo => 590 508.918032 modulo 750 = 258 meter.

After that we are able to calculate the Latitude and Longitude chunks.

Latitude chunk position = 333 331.873931 - 331 = 333 000.873931.
Longitude chunk position = 590 508.918032 -258 = 590 250.918032.

When we didive that now through the chunk size we get the actual chunk position.

Chunk X = 333 000.873931 / 750 = 444,0011.
Chunk Y = 590 250.918032 / 750 = 787,001.

Rounded: Chunk X = 444; Chunk Y = 787.

Now we know that the player stands right in this chunk [444;787].

In the next step I just calculate the Chunk X and Y back to latitude and 
longitude and convert them via transform.AsUnityPosition to 3D coordinates, 
where I place those grey objects then.

So basically I need a way to have the same visual distance between each of those chunks. Or either a way to change the projection from Mercator to a square projection, where nothing is stretched. I never worked with mapbox/Google Maps before... so I'm a newbie. Or is there maybe a formula I can use to prevent my chunks from being stretched by the projection?

Thanks for your time and attention! :)

\$\endgroup\$
12
  • 2
    \$\begingroup\$ welcome to cartography and the rabithole of map projections. \$\endgroup\$ Dec 1, 2017 at 16:26
  • \$\begingroup\$ @ratchetfreak yep... this problem annoys me since a few weeks already :/ \$\endgroup\$
    – genaray
    Dec 1, 2017 at 16:43
  • \$\begingroup\$ I has annoyed mankind for centuries. \$\endgroup\$ Dec 1, 2017 at 16:49
  • 1
    \$\begingroup\$ It is impossible to create a flat map from a sphere. There are three key things you'd want to preserve: distance of straight lines, angles between straight lines, and of course, straight lines should themselves remain straight. Draw a line from the equator, straight up to the north pole. Turn right 90 degrees and return to the equator. Turn right 90 degrees once more and return to your starting position. This triangle cannot be represented accurately on a 2D sheet of paper \$\endgroup\$ Dec 1, 2017 at 16:56
  • 2
    \$\begingroup\$ By turning the data into a sphere again, yes. By keeping the data flat? No. What part of "It is impossible to create a flat map from a sphere" didn't make sense? \$\endgroup\$ Dec 1, 2017 at 19:11

1 Answer 1

2
\$\begingroup\$

If you want to use a Mercator projection, but have each chunk the same size, then you need to diminish the latitude span as you proceed away from the equator.

For the following examples I'll use a simplified Mercator-style projection that models the Earth as a sphere. If you're making use of a particular source of map/GIS data, you'll need to adjust this to use the appropriate ellipsoid/datum/etc. of your data source of choice.

This function gives us the transformation between latitude & longitude on the globe, and 2D coordinates on our map plane:

Vector2 mapCoordinates(float latitude, float longitude) {
    return new Vector2(longitude, Mathf.Rad2Deg * Mathf.Tan(latitude *  Mathf.Deg2Rad));
}

The map coordinates run from -180 to 180 horizontally and -infinity to +infinity vertically by default, but you can scale the result differently as needed.

Let's say we're dividing the surface into n chunks around the equator, so each chunk spans longitudePerChunk = 360/n degrees of longitude.

We'll put our first row-dividing line along the equator, and stack rows of chunks northward and southward from there.

To get our first row of chunks to be square in size, we want the y coordinate of their top line to come out to the same value as longitudePerChunk, and their centers at half that.

firstRowTopLatitude = Mathf.Atan(longitudePerChunk * Mathf.Deg2Rad) * Mathf.Rad2Deg;

firstRotCenterLatitude = Mathf.Atan(longiduePerChunk * 0.5f * Mathf.Deg2Rad) * Mathf.Rad2Deg;

And we can generalize this. The ith row of chunks (starting from 0)...

  • Runs from bottomLatitude[i] = Mathf.Atan(longitudePerChunk * i * Mathf.Deg2Rad) * Mathf.Rad2Deg

  • ...up to topLatitude[i] = Mathf.Atan(longitudePerChunk * (i + 1) * Mathf.Deg2Rad) * Mathf.Rad2Deg

  • ...centered at centerLatitude[i] = Mathf.Atan(longitudePerChunk * (i + 0.5f) * Mathf.Deg2Rad) * Mathf.Rad2Deg

This gets you even spacing of lines/rows of chunks on your 2D plane. Note though that each chunk represents an ever-diminishing amount of surface area on the original globe. (Because the globe gets smaller and smaller in circumference as we move toward the poles, but our projection is forcing it to maintain the same width on the 2D plane, and scaling the vertical to match conformally)

You can compute the effective scale factor at a given chunk by the formula

scale = 1f/Mathf.Cos(centerLatitude * Mathf.Deg2Rad);

For for instance, for n = 128, our first row has an average scale around 1.0003x. By the tenth row above the equator, our map scale is up to 1.1x. By the time we reach Canada around the 24th row, it's 1.5x. We hit 2x scale by the time we get to Yellowknife around row 40, and it keeps accelerating from there. Alert is up around row 145, where the scale is over 7x.


You could also switch to something like an equirectangular projection, and use constant latitude & longitude strides between chunks. Here the challenge is that the scaling you encounter is non-uniform: each span counts for less and less distance horizontally as we approach the poles, while the vertical scale stays the same.

Or you could use still other map projections like Gall-Peters or any of the other hundreds of options. ;) Selecting one would require more details about your application and needs.

\$\endgroup\$
6
  • \$\begingroup\$ Thanks a Lot for this answer ! As soon as im home im gonna try it and hopefully i understood everything right :). And can i ask a few questions in case of doubt ? ^^ \$\endgroup\$
    – genaray
    Dec 1, 2017 at 22:13
  • \$\begingroup\$ What exactly does : Vector2 mapCoordinates(float latidude, float longitude) { return new Vector2(longitude, Mathf.Tan(latitude * Mathf.Deg2Rad)); } do ? And when i should use it ? O.o \$\endgroup\$
    – genaray
    Dec 3, 2017 at 14:44
  • \$\begingroup\$ Right... so in my post i wrote the way how i do calculate my chunks. When i click somewhere on the map to calculate my chunk, the chunk apears at an different spot instead at the position i clicked ... How do i use your solution exactly ? In my case i need to get the chunk where the spefific lat/lng is in. \$\endgroup\$
    – genaray
    Dec 3, 2017 at 15:08
  • \$\begingroup\$ If you know the position on the map plane, you can divide it by your longitudePerChunk and floor it to get the row & column it should be in within your chunk grid. Then you can run that row through the formulas above to get the latitudes of its top/center/bottom as needed. To go the other way, from a known latitude/longitude you can just pass it through the mapCoordinates method above to get the position on your map plane, and again divide by longitudePerChunk and floor it to get which chunk it falls inside. \$\endgroup\$
    – DMGregory
    Dec 3, 2017 at 15:12
  • \$\begingroup\$ Could you maybe give me a full example where you convert a lat/lng position into the chunk ? I honestly have no real idea how to use your solution. That would be very nice, but you dont need to :) \$\endgroup\$
    – genaray
    Dec 3, 2017 at 15:25

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .