-
Notifications
You must be signed in to change notification settings - Fork 2
Map Engines
This article is about map engines (Leaflet, etc.). For the game engines see Tools.
There are a few usable map engines. Most of them use standard geographical CRS (Coordinate Reference System, or Spatial Reference System), by default. Game maps require either transforming all the coordinates or customizing the map CRS.
Leaflet uses an obsolete tile size 256 by default, so set the layer's tileSize
option to 512 first. Then set layer's maxNativeZoom
to your tileset. Basically, if you're using L.CRS.Simple
, then your default coordinate grid is always 512x512, because it's the actual map at zoom level 0. Then you can adjust crs to match the dimensions. Sadly you can't swap x and y.
// w, h, x, y are map size and center
let factor = 1 / tileSize;
let dx = tileSize * (0.5 - x / w);
let dy = tileSize * (0.5 - y / w);
let crs = L.CRS.Simple;
crs.transformation = new L.Transformation(factor, dx, (flip_y ? -1 : 1) * factor, dy);
The flip_y variable depends on the game, CRS.Simple assumes that Y grows down from the left top corner.
- In the Unreal Engine you get a system where X grows right (from top left), Y grows down, so it matches Simple CRS.
- In the Creation Engine (Bethesda) X grows right (from the center), Y grows up, so centering and flip_y is needed.
let d = getMapSize(mapId); // map size in game units (e.g. map width in centimeters)
let m = {
size: d,
bounds: [[0,0], [d-1,d-1]],
center: [d/2, d/2],
flip_y: false,
}
let tileSize = 512; // this is essentially map size in tiles on zoom level 0
let size = tileSize*tileSize; // default map width in pixels is tileSize squared
// custom map weighted (scaled) dimensions (usually 0..1)
let [sw,sh] = [m.size, m.size].map(e => e / size);
let [sx,sy] = m.center.map(e => e / size);
// calculate factor and offset for the custom map
let factor = 1 / tileSize / sw;
let dx = (.5 - sx / sw) * tileSize;
let dy = (.5 - sy / sh) * tileSize;
// correct bounds (still need to flip x and y for leaflet)
let [w,h] = [m.size, m.size];
let [x,y] = m.center;
let [[left,top],[right,bottom]] = m.bounds;
let bounds = [[top, left], [bottom, right]];
let center = [(m.flip_y ? -1 : 1) * y, x];
// crs.transformation represents an affine transformation:
let crs = L.CRS.Simple;
// a set of coefficients a, b, c, d for transforming a point of a form (x,y) into (a*x + b, c*y + d)
crs.transformation = new L.Transformation(factor, dx, (m.flip_y ? -1 : 1) * factor, dy); // Invert the y-axis
map = L.map('map', {
crs: crs,
zoom: 1, // mandatory, need to set zoom and center first (c)
center: center, // mandatory
maxBounds: L.latLngBounds(bounds).pad(0.5), // elastic-y bounds, nice to have
});
L.tileLayer('https://joric.github.io/stalker2_tileset/tiles/{z}/{x}/{y}.jpg', {
tileSize: 512,
maxNativeZoom: 7,
bounds: bounds, // mandatory to hide 404 errors
}).addTo(map);
Drawing a lot of markers would need an OpenGL layer (e.g. Leaflet.PixiOverlay) or Leaflet.markercluster.
PixiOverlay API is pretty simple but the performance is bad, my OpenGL setup stuttered at 50k+ markers (canvas renderer becomes unusable at about 5k markers).
MarkerCluster plugin is pretty good. On changes it's faster to reload all markers using the chunkedLoading
option.
- https://github.com/maptalks/maptalks.js
- https://maptalks.org/maptalks.js/api/0.x/Map.html (map class API)
let w = h = 812900;
let center = [w/2, h/2];
let [left,top,right,bottom] = [0, 0, w, h];
let tileSize = 512;
let maxZoom = 19;
map = new maptalks.Map('map', {
center: center,
maxZoom: maxZoom,
spatialReference : {
projection : 'identity',
fullExtent : { top: top, left: left, bottom: bottom, right: right }, // mandatory to hide 404 errors
resolutions: Array.from({length: maxZoom + 1},(_,i) => w / tileSize / (1<<i)), // mandatory zoom levels
},
// ...
});
new maptalks.TileLayer('PDA', {
maxAvailableZoom: 7,
urlTemplate: 'https://joric.github.io/stalker2_tileset/tiles/{z}/{x}/{y}.jpg',
repeatWorld: false,
tileSize: 512,
}).addTo(map);
Maptalks introduces OpenGL layers (100k+ markers at once). To add OpenGL image markers simply use PointLayer
.
See https://github.com/maptalks/maptalks.js/issues/2486:
const layer = new PointLayer('points', [markers]).addTo(map);
- Everything is almost the same with rendering markers with VectorLayer
- except PointLayer only support markers or MultiPoints and several additional symbols
- so we have another two types of layers: LineStringLayer and PolygonLayer for lines and polygons
- Currently, every webgl layers has an independent webgl context , so the total number of webgl layers you can create is limited by browser.
- you can add webgl layers to a container layer called GroupGLLayer to let them share the webgl context and depth buffer
- in the coming [email protected] version, all the layers will share one global webgl context, so number of layers will not be limited and performance will be significantly improved.
const group = new GroupGLLayer('group', []).addTo(map);
const layer = new PointLayer('points', [markers]).addTo(group);
Proprietary commercial license, wants your credit card for the API key.
JSON-based config. Doesn't seem to support CRS customization (#3890).