Skip to content

Commit

Permalink
enable tilelayer have independent spatial-reference, fix #561
Browse files Browse the repository at this point in the history
  • Loading branch information
fuzhenn committed Dec 20, 2017
1 parent e032b7e commit 31ce64f
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 85 deletions.
7 changes: 5 additions & 2 deletions src/core/util/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,14 +178,17 @@ export function interpolate(a, b, t) {
}

/*
* constrain n to the given range, excluding the minimum, via modular arithmetic
* constrain n to the given range, via modular arithmetic
* @param {Number} n value
* @param {Number} min the minimum value to be returned, exclusive
* @param {Number} min the minimum value to be returned, inclusive
* @param {Number} max the maximum value to be returned, inclusive
* @returns {Number} constrained number
* @private
*/
export function wrap(n, min, max) {
if (n === max || n === min) {
return n;
}
const d = max - min;
const w = ((n - min) % d + d) % d + min;
return w;
Expand Down
142 changes: 91 additions & 51 deletions src/layer/tile/TileLayer.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import { IS_NODE, isNil, isArrayHasData, isFunction, isInteger } from 'core/util';
import Browser from 'core/Browser';
import Point from 'geo/Point';
import Size from 'geo/Size';
import PointExtent from 'geo/PointExtent';
import TileConfig from './tileinfo/TileConfig';
import TileSystem from './tileinfo/TileSystem';
import Layer from '../Layer';
import SpatialReference from '../../map/spatial-reference/SpatialReference';

/**
* @property {Object} options - TileLayer's options
* @property {String|Function} options.urlTemplate - url templates
* @property {String[]|Number[]} [options.subdomains=null] - subdomains to replace '{s}' in urlTemplate
* @property {Object} [options.spatialReference=null] - TileLayer's spatial reference
* @property {Number[]} [options.tileSize=[256, 256]] - size of the tile image, [width, height]
* @property {Number[]} [options.tileSystem=null] - tile system number arrays
* @property {Boolean} [options.repeatWorld=true] - tiles will be loaded repeatedly outside the world.
* @property {Boolean} [options.zoomBackground=false] - whether to draw a background of baselayer during or after zooming, false by default
* @property {String} [options.fragmentShader=null] - custom fragment shader, replace <a href="https://github.com/maptalks/maptalks.js/blob/master/src/renderer/layer/tilelayer/TileLayerGLRenderer.js#L8">the default fragment shader</a>
* @property {String} [options.crossOrigin=null] - tile image's corssOrigin
* @property {Number[]} [options.tileSize=[256, 256]] - size of the tile image, [width, height]
* @property {Number[]} [options.tileSystem=null] - tile system number arrays
* @property {Boolean} [options.fadeAnimation=true] - fade animation when loading tiles
* @property {Boolean} [options.debug=false] - if set to true, tiles will have borders and a title of its coordinates.
* @property {String} [options.renderer=gl] - TileLayer's renderer, canvas or gl. gl tiles requires image CORS that canvas doesn't. canvas tiles can't pitch.
Expand All @@ -41,6 +42,8 @@ const options = {

'debug': false,

'spatialReference' : null,

'renderer' : (() => {
return Browser.webgl ? 'gl' : 'canvas';
})()
Expand Down Expand Up @@ -147,10 +150,7 @@ class TileLayer extends Layer {
_getTiles(z) {
// rendWhenReady = false;
const map = this.getMap();
if (!map) {
return null;
}
if (!this.isVisible()) {
if (!map || !this.isVisible() || !map.width || !map.height) {
return null;
}

Expand All @@ -159,24 +159,12 @@ class TileLayer extends Layer {
return null;
}

const tileSize = this.getTileSize(),
width = tileSize['width'],
height = tileSize['height'];
const zoom = isNil(z) ? this._getTileZoom() : z;

const res = map.getResolution(zoom),
extent2d = map._get2DExtent(zoom),
containerCenter = new Point(map.width / 2, map.height / 2),
center2d = map._containerPointToPoint(containerCenter, zoom);
const emptyGrid = {
'zoom' : zoom,
'anchor' : null,
'extent' : null,
'tiles' : []
};
if (extent2d.getWidth() === 0 || extent2d.getHeight() === 0) {
return emptyGrid;
}

let containerExtent = map.getContainerExtent();
const maskExtent = this._getMask2DExtent();
Expand All @@ -187,45 +175,73 @@ class TileLayer extends Layer {
}
containerExtent = intersection.convertTo(c => map._pointToContainerPoint(c));
}
const sr = this._sr;
const mapSR = map.getSpatialReference();
const res = sr.getResolution(zoom);

//Get description of center tile including left and top offset
const centerTile = tileConfig.getCenterTile(map._getPrjCenter(), res),
offset = centerTile['offset'],
center2D = map._prjToPoint(map._getPrjCenter(), zoom)._sub(offset.x, offset.y),
mapVP = map.getViewPoint();
const c = this._project(map._getPrjCenter());
const extent2d = map._get2DExtent(),
center2D = extent2d.getCenter();
const pmin = this._project(map._pointToPrj(extent2d.getMin(), zoom)),
pmax = this._project(map._pointToPrj(extent2d.getMax(), zoom));

const scale = map.getResolution() / res,
centerVP = containerCenter.sub((scale !== 1 ? mapVP.multi(scale) : mapVP))._sub(offset.x, offset.y)._round();
const centerTile = tileConfig.getTileIndex(c, res),
ltTile = tileConfig.getTileIndex(pmin, res),
rbTile = tileConfig.getTileIndex(pmax, res);

const keepBuffer = 1;
const keepBuffer = 0;

//Number of tiles around the center tile
const top = Math.ceil(Math.abs(center2d.y - extent2d.ymin - offset.y) / height) + keepBuffer,
left = Math.ceil(Math.abs(center2d.x - extent2d.xmin - offset.x) / width) + keepBuffer,
bottom = Math.ceil(Math.abs(extent2d.ymax - center2d.y + offset.y) / height) + keepBuffer,
right = Math.ceil(Math.abs(extent2d.xmax - center2d.x + offset.x) / width) + keepBuffer;
const top = Math.ceil(Math.abs(centerTile.y - ltTile.y)) + keepBuffer,
left = Math.ceil(Math.abs(centerTile.x - ltTile.x)) + keepBuffer,
bottom = Math.ceil(Math.abs(centerTile.y - rbTile.y)) + keepBuffer,
right = Math.ceil(Math.abs(centerTile.x - rbTile.x)) + keepBuffer;
const layerId = this.getId();
const tileSize = this.getTileSize();
const tiles = [];
for (let i = -(left); i < right; i++) {
for (let j = -(top); j < bottom; j++) {
const p = new Point(center2D.x + width * i, center2D.y + height * j),
vp = new Point(centerVP.x + width * i, centerVP.y + height * j),
idx = tileConfig.getNeighorTileIndex(centerTile['x'], centerTile['y'], i, j, res, this.options['repeatWorld']),
url = this.getTileUrl(idx['x'], idx['y'], zoom),
id = [layerId, idx['idy'], idx['idx'], zoom].join('__'),
tileExtent = new PointExtent(p, p.add(width, height)),
const scale = this._getTileConfig().tileSystem.scale;
const extent = new PointExtent();
for (let i = -(left); i <= right; i++) {
for (let j = -(top); j <= bottom; j++) {
const idx = tileConfig.getNeighorTileIndex(centerTile['x'], centerTile['y'], i, j, res, this.options['repeatWorld']),
url = this.getTileUrl(idx.x, idx.y, zoom),
id = [layerId, idx.idy, idx.idx, zoom].join('__'),
pnw = tileConfig.getTilePrjNW(idx.x, idx.y, res),
p = map._prjToPoint(this._unproject(pnw), zoom);
let width, height;
if (sr === mapSR) {
width = tileSize.width;
height = tileSize.height;
} else {
const pse = tileConfig.getTilePrjSE(idx.x, idx.y, res),
pp = map._prjToPoint(this._unproject(pse), zoom);
width = Math.abs(Math.round(pp.x - p.x));
height = Math.abs(Math.round(pp.y - p.y));
}
const dx = scale.x * (idx.idx - idx.x) * width,
dy = -scale.y * (idx.idy - idx.y) * height;
if (dx || dy) {
p._add(dx, dy);
}
if (sr !== mapSR) {
width++; //plus 1 to prevent white gaps
height++;
}
const tileExtent = new PointExtent(p, p.add(width, height)),
tileInfo = {
'url': url,
'point': p,
'viewPoint' : vp,
'id': id,
'z': zoom,
'x' : idx['x'],
'y' : idx['y'],
'extent2d' : tileExtent
'x' : idx.x,
'y' : idx.y,
'extent2d' : tileExtent,
'size' : [width, height]
};
if (this._isTileInExtent(tileInfo, containerExtent)) {
tiles.push(tileInfo);
extent._combine(tileExtent);
}
}
}
Expand All @@ -235,31 +251,54 @@ class TileLayer extends Layer {
return (b.point.distanceTo(center2D) - a.point.distanceTo(center2D));
});

//tile's view point at 0, 0, zoom
const tileSystem = tileConfig.tileSystem;
const anchor = centerVP.sub(centerTile.x * width * tileSystem.scale.x, -centerTile.y * height * tileSystem.scale.y);
anchor.zoom = zoom;
return {
'zoom' : zoom,
'anchor' : anchor,
'extent' : new PointExtent(center2D - left * width, center2D - top * height, center2D + right * width, center2D + bottom * height),
'extent' : extent,
'tiles': tiles
};
}

_project(pcoord) {
const map = this.getMap();
const sr = this._sr;
if (sr !== map.getSpatialReference()) {
return sr.getProjection().project(map.getProjection().unproject(pcoord));
} else {
return pcoord;
}
}

_unproject(pcoord) {
const map = this.getMap();
const sr = this._sr;
if (sr !== map.getSpatialReference()) {
return map.getProjection().project(sr.getProjection().unproject(pcoord));
} else {
return pcoord;
}
}

/**
* initialize [tileConfig]{@link TileConfig} for the tilelayer
* @private
*/
_initTileConfig() {
const map = this.getMap(),
sr = this.options['spatialReference'] ? new SpatialReference(this.options['spatialReference']) : map.getSpatialReference(),
tileSize = this.getTileSize();
this._defaultTileConfig = new TileConfig(TileSystem.getDefault(map.getProjection()), map.getFullExtent(), tileSize);
this._sr = sr;
const projection = sr.getProjection(),
fullExtent = sr.getFullExtent();
this._defaultTileConfig = new TileConfig(TileSystem.getDefault(projection), fullExtent, tileSize);
if (this.options['tileSystem']) {
this._tileConfig = new TileConfig(this.options['tileSystem'], map.getFullExtent(), tileSize);
this._tileConfig = new TileConfig(this.options['tileSystem'], fullExtent, tileSize);
}
//inherit baselayer's tileconfig
if (map && map.getBaseLayer() && map.getBaseLayer() !== this && map.getBaseLayer()._getTileConfig) {
if (map &&
map.getSpatialReference() === sr &&
map.getBaseLayer() &&
map.getBaseLayer() !== this &&
map.getBaseLayer()._getTileConfig) {
const base = map.getBaseLayer()._getTileConfig();
this._tileConfig = new TileConfig(base.tileSystem, base.fullExtent, tileSize);
}
Expand Down Expand Up @@ -343,6 +382,7 @@ class TileLayer extends Layer {
_onSpatialReferenceChange() {
delete this._tileConfig;
delete this._defaultTileConfig;
delete this._sr;
}
}

Expand Down
51 changes: 33 additions & 18 deletions src/layer/tile/tileinfo/TileConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class TileConfig {
* @param {Number} res - current resolution
* @return {Object} tile index
*/
getTileIndex(point, res) {
_getTileNum(point, res) {
const tileSystem = this.tileSystem,
tileSize = this['tileSize'],
delta = 1E-7;
Expand All @@ -69,17 +69,17 @@ class TileConfig {
* @param {Number} res - current resolution
* @return {Object} tile index and offset
*/
getCenterTile(pCoord, res) {
const tileSystem = this.tileSystem,
tileSize = this['tileSize'];
getTileIndex(pCoord, res) {
const tileSystem = this.tileSystem;
// tileSize = this['tileSize'];
const point = this.transformation.transform(pCoord, 1);
let tileIndex = this.getTileIndex(point, res);
let tileIndex = this._getTileNum(point, res);

const tileLeft = tileIndex['x'] * tileSize['width'];
const tileTop = tileIndex['y'] * tileSize['height'];
// const tileLeft = tileIndex['x'] * tileSize['width'];
// const tileTop = tileIndex['y'] * tileSize['height'];

const offsetLeft = point.x / res - tileSystem['scale']['x'] * tileLeft;
const offsetTop = point.y / res + tileSystem['scale']['y'] * tileTop;
// const offsetLeft = point.x / res - tileSystem['scale']['x'] * tileLeft;
// const offsetTop = point.y / res + tileSystem['scale']['y'] * tileTop;

//如果x方向为左大右小
if (tileSystem['scale']['x'] < 0) {
Expand All @@ -95,8 +95,8 @@ class TileConfig {

return {
'x': tileIndex['x'],
'y': tileIndex['y'],
'offset': new Point(offsetLeft, offsetTop)
'y': tileIndex['y']/* ,
'offset': new Point(offsetLeft, offsetTop) */
};
}

Expand Down Expand Up @@ -149,24 +149,39 @@ class TileConfig {
_getTileFullIndex(res) {
const ext = this.fullExtent;
const transformation = this.transformation;
const nwIndex = this.getTileIndex(transformation.transform(new Coordinate(ext['left'], ext['top']), 1), res);
const seIndex = this.getTileIndex(transformation.transform(new Coordinate(ext['right'], ext['bottom']), 1), res);
const nwIndex = this._getTileNum(transformation.transform(new Coordinate(ext['left'], ext['top']), 1), res);
const seIndex = this._getTileNum(transformation.transform(new Coordinate(ext['right'], ext['bottom']), 1), res);
return new Extent(nwIndex, seIndex);
}

/**
* Get tile's south west's projected coordinate
* Get tile's north west's projected coordinate
* @param {Number} tileX
* @param {Number} tileY
* @param {Number} res
* @return {Object}
* @return {Number[]}
*/
getTileProjectedSw(tileX, tileY, res) {
getTilePrjNW(tileX, tileY, res) {
const tileSystem = this.tileSystem;
const tileSize = this['tileSize'];
const y = tileSystem['origin']['y'] + tileSystem['scale']['y'] * (tileY + (tileSystem['scale']['y'] === 1 ? 0 : 1)) * (res * tileSize['height']);
const y = tileSystem['origin']['y'] + tileSystem['scale']['y'] * (tileY + (tileSystem['scale']['y'] === 1 ? 1 : 0)) * (res * tileSize['height']);
const x = tileSystem['scale']['x'] * (tileX + (tileSystem['scale']['x'] === 1 ? 0 : 1)) * res * tileSize['width'] + tileSystem['origin']['x'];
return [x, y];
return new Coordinate(x, y);
}

/**
* Get tile's south east's projected coordinate
* @param {Number} tileX
* @param {Number} tileY
* @param {Number} res
* @return {Number[]}
*/
getTilePrjSE(tileX, tileY, res) {
const tileSystem = this.tileSystem;
const tileSize = this['tileSize'];
const y = tileSystem['origin']['y'] + tileSystem['scale']['y'] * (tileY + (tileSystem['scale']['y'] === 1 ? 0 : 1)) * (res * tileSize['height']);
const x = tileSystem['scale']['x'] * (tileX + (tileSystem['scale']['x'] === 1 ? 1 : 0)) * res * tileSize['width'] + tileSystem['origin']['x'];
return new Coordinate(x, y);
}

/**
Expand Down
6 changes: 4 additions & 2 deletions src/layer/tile/tileinfo/TileSystem.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,16 @@ class TileSystem {
}
}

const semiCircum = 6378137 * Math.PI;

extend(TileSystem, /** @lends TileSystem */ {
/**
* The most common used tile system, used by google maps, bing maps and amap, soso maps in China.
* @see {@link https://en.wikipedia.org/wiki/Web_Mercator}
* @constant
* @static
*/
'web-mercator': new TileSystem([1, -1, -20037508.34, 20037508.34]),
'web-mercator': new TileSystem([1, -1, -semiCircum, semiCircum]),

/**
* Predefined tile system for TMS tile system, A tile system published by [OSGEO]{@link http://www.osgeo.org/}. <br>
Expand All @@ -75,7 +77,7 @@ extend(TileSystem, /** @lends TileSystem */ {
* @constant
* @static
*/
'tms-global-mercator': new TileSystem([1, 1, -20037508.34, -20037508.34]),
'tms-global-mercator': new TileSystem([1, 1, -semiCircum, -semiCircum]),

/**
* Another tile system published by [OSGEO]{@link http://www.osgeo.org/}, based on EPSG:4326 SRS.
Expand Down
Loading

0 comments on commit 31ce64f

Please sign in to comment.