feat(control): add scale, layer.zoom control

This commit is contained in:
thinkinggis 2019-08-07 10:44:29 +08:00
parent dbf449e2a8
commit bc74edeb66
36 changed files with 1325 additions and 70 deletions

View File

@ -13,6 +13,7 @@
<script>/*Fixing iframe window.innerHeight 0 issue in Safari*/ document.body.clientHeight; </script> <script>/*Fixing iframe window.innerHeight 0 issue in Safari*/ document.body.clientHeight; </script>
<script src="https://webapi.amap.com/maps?v=1.4.8&key=15cd8a57710d40c9b7c0e3cc120f1200&plugin=Map3D"></script> <script src="https://webapi.amap.com/maps?v=1.4.8&key=15cd8a57710d40c9b7c0e3cc120f1200&plugin=Map3D"></script>
<script src="https://gw.alipayobjects.com/os/antv/assets/lib/jquery-3.2.1.min.js"></script> <script src="https://gw.alipayobjects.com/os/antv/assets/lib/jquery-3.2.1.min.js"></script>
<link rel=stylesheet type=text/css href='../build/l7.css'>
<script src="../build/l7.js"></script> <script src="../build/l7.js"></script>
<style> <style>
#map { position:absolute; top:0; bottom:0; width:100%; } #map { position:absolute; top:0; bottom:0; width:100%; }
@ -28,7 +29,7 @@
window.scene = scene; window.scene = scene;
scene.on('loaded', function() { scene.on('loaded', function() {
$.get('https://gw.alipayobjects.com/os/rmsportal/epnZEheZeDgsiSjSPcCv.json', function(data) { $.get('https://gw.alipayobjects.com/os/rmsportal/epnZEheZeDgsiSjSPcCv.json', function(data) {
scene.PointLayer({ const layer = scene.PointLayer({
zIndex: 2 zIndex: 2
}).source(data, { }).source(data, {
scale: { scale: {
@ -44,7 +45,16 @@
strokeWidth: 1, strokeWidth: 1,
opacity: 0.9 opacity: 0.9
}).render(); }).render();
layer.on('click',(e) => {
const { lnglat, feature } = e;
const popup = new L7.Popup()
.setLnglat([lnglat.lng, lnglat.lat])
.setHTML('hello').addTo(scene);
})
}); });
}); });
</script> </script>
</body> </body>

BIN
demos/images/layers-2x.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
demos/images/layers.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

View File

@ -30,10 +30,21 @@ const scene = new L7.Scene({
pitch: 0, pitch: 0,
hash:false, hash:false,
zoom: 3, zoom: 3,
maxZoom:7,
}); });
// 高德数据服务 https://mvt.amap.com/district/CHN2/{z}/{x}/{y}/4096?key=608d75903d29ad471362f8c58c550daf // 高德数据服务 https://mvt.amap.com/district/CHN2/{z}/{x}/{y}/4096?key=608d75903d29ad471362f8c58c550daf
scene.on('loaded', () => { scene.on('loaded', () => {
const ZoomControl = new L7.Control.Zoom();
ZoomControl.addTo(scene);
const scaleControl = new L7.Control.Scale();
scaleControl.addTo(scene);
const attributeCtr = new L7.Control.Attribution();
attributeCtr.addTo(scene);
scene.addTileSource('test',{ scene.addTileSource('test',{
url:'http://127.0.0.1:8080/{z}/{x}/{y}.pbf', url:'http://127.0.0.1:8080/{z}/{x}/{y}.pbf',
type:'vector', type:'vector',
@ -52,13 +63,19 @@ scene.on('loaded', () => {
} }
}) })
.shape('fill') .shape('fill')
.active(false) .active(true)
//.color('adcode_cit',['#d53e4f','#f46d43','#fdae61','#fee08b','#ffffbf','#e6f598','#abdda4','#66c2a5','#3288bd']) //.color('adcode_cit',['#d53e4f','#f46d43','#fdae61','#fee08b','#ffffbf','#e6f598','#abdda4','#66c2a5','#3288bd'])
.color('#f46d43') .color('#f46d43')
.style({ .style({
opacity:1.0 opacity:1.0
}) })
.render(); .render();
layer.on('click',(e) => {
const { lnglat, feature } = e;
const popup = new L7.Popup()
.setLnglat([lnglat.lng, lnglat.lat])
.setHTML(feature.properties.id).addTo(scene);
})
const layer2 = scene.PolygonLayer({ const layer2 = scene.PolygonLayer({
zIndex:10, zIndex:10,
}) })
@ -96,7 +113,11 @@ scene.on('loaded', () => {
opacity:1.0 opacity:1.0
}) })
.render(); .render();
const overlayers = {
"行政区划": layer,
"标注": layer3,
};
const layerContr = new L7.Control.Layers({overlayers}).addTo(scene);
}); });

View File

@ -51,6 +51,8 @@
"rollup": "^1.16.2", "rollup": "^1.16.2",
"rollup-plugin-buble": "^0.19.6", "rollup-plugin-buble": "^0.19.6",
"rollup-plugin-commonjs": "^9.2.1", "rollup-plugin-commonjs": "^9.2.1",
"rollup-plugin-css-porter": "^1.0.2",
"rollup-plugin-image": "^1.0.2",
"rollup-plugin-json": "^4.0.0", "rollup-plugin-json": "^4.0.0",
"rollup-plugin-node-builtins": "^2.1.2", "rollup-plugin-node-builtins": "^2.1.2",
"rollup-plugin-node-resolve": "^4.0.1", "rollup-plugin-node-resolve": "^4.0.1",

View File

@ -1,11 +1,15 @@
import fs from 'fs'; import fs from 'fs';
// import sourcemaps from 'rollup-plugin-sourcemaps'; // import sourcemaps from 'rollup-plugin-sourcemaps';
// import postcss from 'postcss';
import json from 'rollup-plugin-json'; import json from 'rollup-plugin-json';
import buble from 'rollup-plugin-buble'; import buble from 'rollup-plugin-buble';
import resolve from 'rollup-plugin-node-resolve'; import resolve from 'rollup-plugin-node-resolve';
import commonjs from 'rollup-plugin-commonjs'; import commonjs from 'rollup-plugin-commonjs';
import builtins from 'rollup-plugin-node-builtins'; import builtins from 'rollup-plugin-node-builtins';
import { createFilter } from 'rollup-pluginutils'; import { createFilter } from 'rollup-pluginutils';
// import imageInliner from 'postcss-image-inliner';
import postcss from 'rollup-plugin-postcss';
import url from 'postcss-url';
import { terser } from 'rollup-plugin-terser'; import { terser } from 'rollup-plugin-terser';
const { BUILD, MINIFY } = process.env; const { BUILD, MINIFY } = process.env;
const minified = MINIFY === 'true'; const minified = MINIFY === 'true';
@ -39,6 +43,10 @@ const config = [
), ),
minified ? terser() : false, minified ? terser() : false,
json(), json(),
// css({ raw: './build/l7.css' }),
postcss({
plugins: [ url({ url: 'inline' }) ]
}),
buble({ buble({
transforms: { dangerousForOf: true }, transforms: { dangerousForOf: true },
objectAssign: 'Object.assign' objectAssign: 'Object.assign'
@ -68,6 +76,7 @@ const config = [
}, },
treeshake: false, treeshake: false,
plugins: [ plugins: [
// css()
] ]
} }
]; ];

View File

@ -0,0 +1,93 @@
import Control from './base';
import * as DOM from '../../util/dom';
import { toLngLat } from '@antv/geo-coord/lib/geo/geometry/lng-lat';
import { bindAll } from '../../util/event';
export default class Scale extends Control {
constructor(cfg) {
super({
position: 'bottomleft',
maxWidth: 100,
metric: true,
updateWhenIdle: false,
imperial: false,
...cfg
});
bindAll([ '_update' ], this);
}
onAdd(scene) {
const className = 'l7-control-scale';
const container = DOM.create('div', className);
this._addScales(className + '-line', container);
scene.on(this.get('updateWhenIdle') ? 'moveend' : 'mapmove', this._update);
this._update();
return container;
}
onRemove(scene) {
scene.off(this.get('updateWhenIdle') ? 'moveend' : 'mapmove', this._update);
}
_addScales(className, container) {
if (this.get('metric')) {
this._mScale = DOM.create('div', className, container);
}
if (this.get('imperial')) {
this._iScale = DOM.create('div', className, container);
}
}
_update() {
const scene = this._scene;
const y = this._scene.getSize().height / 2;
const p1 = scene.containerToLngLat({ x: 0, y });
const p2 = scene.containerToLngLat({ x: this.get('maxWidth'), y });
const maxMeters = scene.crs.distance(toLngLat(p1.lng, p1.lat), toLngLat(p2.lng, p2.lat));
this._updateScales(maxMeters);
}
_updateScales(maxMeters) {
if (this.get('metric') && maxMeters) {
this._updateMetric(maxMeters);
}
if (this.get('imperial') && maxMeters) {
this._updateImperial(maxMeters);
}
}
_updateMetric(maxMeters) {
const meters = this._getRoundNum(maxMeters),
label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';
this._updateScale(this._mScale, label, meters / maxMeters);
}
_updateImperial(maxMeters) {
const maxFeet = maxMeters * 3.2808399;
let maxMiles,
miles,
feet;
if (maxFeet > 5280) {
maxMiles = maxFeet / 5280;
miles = this._getRoundNum(maxMiles);
this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);
} else {
feet = this._getRoundNum(maxFeet);
this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
}
}
_updateScale(scale, text, ratio) {
scale.style.width = Math.round(this.get('maxWidth') * ratio) + 'px';
scale.innerHTML = text;
}
_getRoundNum(num) {
const pow10 = Math.pow(10, (Math.floor(num) + '').length - 1);
let d = num / pow10;
d = d >= 10 ? 10 :
d >= 5 ? 5 :
d >= 3 ? 3 :
d >= 2 ? 2 : 1;
return pow10 * d;
}
}

View File

@ -0,0 +1,75 @@
import Control from './base';
import * as DOM from '../../util/dom';
export default class Attribution extends Control {
constructor(cfg) {
super({
position: 'bottomright',
prefix: '<a href="https://antv.alipay.com/zh-cn/l7/1.x/index.html" title="地理空间数据可视化引擎">AntV L7</a>',
...cfg
});
this._attributions = {};
}
onAdd(scene) {
scene.attributionControl = this;
this._container = DOM.create('div', 'l7-control-attribution');
const layers = scene.getLayers();
for (const i in layers) {
if (layers[i].get('attribution')) {
this.addAttribution(layers[i].get('attribution'));
}
}
this._update();
return this._container;
}
setPrefix(prefix) {
this.set('prefix', prefix);
this._update();
return this;
}
addAttribution(text) {
if (!text) { return this; }
if (!this._attributions[text]) {
this._attributions[text] = 0;
}
this._attributions[text]++;
this._update();
return this;
}
removeAttribution(text) {
if (!text) { return this; }
if (this._attributions[text]) {
this._attributions[text]--;
this._update();
}
return this;
}
_update() {
if (!this._scene) { return; }
const attribs = [];
for (const i in this._attributions) {
if (this._attributions[i]) {
attribs.push(i);
}
}
const prefixAndAttribs = [];
if (this.get('prefix')) {
prefixAndAttribs.push(this.get('prefix'));
}
if (attribs.length) {
prefixAndAttribs.push(attribs.join(', '));
}
this._container.innerHTML = prefixAndAttribs.join(' | ');
}
}

View File

@ -0,0 +1,52 @@
import Base from '../../core/base';
import * as DOM from '../../util/dom';
export default class Control extends Base {
constructor(cfg) {
super({
position: 'topright',
...cfg
});
}
setPosition(position) {
const scene = this._scene;
if (scene) {
scene.removeControl(this);
}
this.set('position', position);
if (scene) {
scene.addControl(this);
}
return this;
}
getContainer() {
return this._container;
}
addTo(scene) {
this.remove();
this._scene = scene;
const container = this._container = this.onAdd(scene);
const pos = this.get('position');
const corner = scene.get('controlController')._controlCorners[pos];
DOM.addClass(container, 'l7-control');
if (pos.indexOf('bottom') !== -1) {
corner.insertBefore(container, corner.firstChild);
} else {
corner.appendChild(container);
}
return this;
}
remove() {
if (!this._scene) {
return this;
}
DOM.remove(this._container);
}
_refocusOnMap(e) {
// if map exists and event is not a keyboard event
if (this._scene && e && e.screenX > 0 && e.screenY > 0) {
this._scene.getContainer().focus();
}
}
}

View File

@ -0,0 +1,13 @@
import Control from './base';
import Zoom from './zoom';
import Scale from './Scale';
import Attribution from './attribution';
import Layers from './layer';
export {
Control,
Zoom,
Scale,
Attribution,
Layers
};

View File

@ -0,0 +1,273 @@
import Control from './base';
import * as DOM from '../../util/dom';
import { bindAll } from '../../util/event';
export default class Layers extends Control {
constructor(cfg) {
super({
collapsed: true,
position: 'topright',
autoZIndex: true,
hideSingleBase: false,
sortLayers: false,
...cfg
});
this._layerControlInputs = [];
this._layers = [];
this._lastZIndex = 0;
this._handlingClick = false;
const baseLayers = this.get('baseLayers');
const overlays = this.get('overlayers');
for (const i in baseLayers) {
this._addLayer(baseLayers[i], i);
}
for (const i in overlays) {
this._addLayer(overlays[i], i, true);
}
bindAll([ '_checkDisabledLayers', '_onLayerChange', 'collapse', 'extend', 'expand', '_onInputClick' ], this);
}
onAdd(scene) {
this._initLayout();
this._update();
this._scene = scene;
scene.on('zoomend', this._checkDisabledLayers, this);
for (let i = 0; i < this._layers.length; i++) {
this._layers[i].layer.on('remove', this._onLayerChange);
this._layers[i].layer.on('add', this._onLayerChange);
}
return this._container;
}
addTo(scene) {
super.addTo(scene);
}
addVisualLayer(layer, name) {
this._addLayer(layer, name, true);
return (this._scene) ? this._update() : this;
}
_initLayout() {
const className = 'l7-control-layers',
container = this._container = DOM.create('div', className),
collapsed = this.get('collapsed');
// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
container.setAttribute('aria-haspopup', true);
const form = this._form = DOM.create('form', className + '-list');
if (collapsed) {
this._scene.on('click', this.collapse);
container.addEventListener('mouseenter', this.expand);
container.addEventListener('mouseleave', this.collapse);
}
const link = this._layersLink = DOM.create('a', className + '-toggle', container);
link.href = '#';
link.title = 'Layers';
if (!collapsed) {
this.expand();
}
this._baseLayersList = DOM.create('div', className + '-base', form);
this._separator = DOM.create('div', className + '-separator', form);
this._overlaysList = DOM.create('div', className + '-overlays', form);
container.appendChild(form);
}
_update() {
if (!this._container) { return this; }
DOM.empty(this._baseLayersList);
DOM.empty(this._overlaysList);
this._layerControlInputs = [];
let baseLayersPresent,
overlaysPresent,
i,
obj,
baseLayersCount = 0;
for (i = 0; i < this._layers.length; i++) {
obj = this._layers[i];
this._addItem(obj);
overlaysPresent = overlaysPresent || obj.overlay;
baseLayersPresent = baseLayersPresent || !obj.overlay;
baseLayersCount += !obj.overlay ? 1 : 0;
}
// Hide base layers section if there's only one layer.
if (this.get('hideSingleBase')) {
baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
}
this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';
return this;
}
expand() {
DOM.addClass(this._container, 'l7-control-layers-expanded');
this._form.style.height = null;
const acceptableHeight = this._scene.getSize().height - (this._container.offsetTop + 50);
if (acceptableHeight < this._form.clientHeight) {
DOM.addClass(this._form, 'l7-control-layers-scrollbar');
this._form.style.height = acceptableHeight + 'px';
} else {
DOM.removeClass(this._form, 'l7-control-layers-scrollbar');
}
this._checkDisabledLayers();
return this;
}
collapse() {
DOM.removeClass(this._container, 'l7-control-layers-expanded');
return this;
}
_checkDisabledLayers() {
const inputs = this._layerControlInputs;
let input,
layer;
const zoom = this._scene.getZoom();
for (let i = inputs.length - 1; i >= 0; i--) {
input = inputs[i];
layer = this._scene.getLayer(input.layerId);
input.disabled = (layer.get('minZoom') !== undefined && zoom < layer.get('minZoom')) ||
(zoom < layer.get('maxZoom') !== undefined && zoom > layer.get('maxZoom'));
}
}
_addLayer(layer, name, overlay) {
if (this._scene) {
layer.on('add', this._onLayerChange);
layer.on('remove', this._onLayerChange);
}
this._layers.push({
layer,
name,
overlay
});
if (this.get('sortLayers')) {
this._layers.sort((a, b) => {
return this.get('sortFunction')(a.layer, b.layer, a.name, b.name);
});
}
if (this.get('autoZIndex') && layer.setZIndex) {
this._lastZIndex++;
layer.setZIndex(this._lastZIndex);
}
this._expandIfNotCollapsed();
}
_expandIfNotCollapsed() {
if (this._scene && !this.get('collapsed')) {
this.expand();
}
return this;
}
_onLayerChange(e) {
if (!this._handlingClick) {
this._update();
}
const obj = this._scene.getLayer(e.target.layerId);
const type = obj.overlay ?
(e.type === 'add' ? 'overlayadd' : 'overlayremove') :
(e.type === 'add' ? 'baselayerchange' : null);
if (type) {
this._map.fire(type, obj);
}
}
_createRadioElement(name, checked) {
const radioHtml = '<input type="radio" class="l7-control-layers-selector" name="' +
name + '"' + (checked ? ' checked="checked"' : '') + '/>';
const radioFragment = document.createElement('div');
radioFragment.innerHTML = radioHtml;
return radioFragment.firstChild;
}
_addItem(obj) {
const label = document.createElement('label'),
checked = !!this._scene.getLayer(obj.layer.layerId);
let input;
if (obj.overlay) {
input = document.createElement('input');
input.type = 'checkbox';
input.className = 'l7-control-layers-selector';
input.defaultChecked = checked;
} else {
input = this._createRadioElement('l7-base-layers', checked);
}
this._layerControlInputs.push(input);
input.layerId = obj.layer.layerId;
input.addEventListener('click', this._onInputClick);
const name = document.createElement('span');
name.innerHTML = ' ' + obj.name;
const holder = document.createElement('div');
label.appendChild(holder);
holder.appendChild(input);
holder.appendChild(name);
const container = obj.overlay ? this._overlaysList : this._baseLayersList;
container.appendChild(label);
this._checkDisabledLayers();
return label;
}
_onInputClick() {
const inputs = this._layerControlInputs;
let input,
layer;
const addedLayers = [],
removedLayers = [];
this._handlingClick = true;
for (let i = inputs.length - 1; i >= 0; i--) {
input = inputs[i];
layer = this._scene.getLayer(input.layerId);
if (input.checked) {
addedLayers.push(layer);
} else if (!input.checked) {
removedLayers.push(layer);
}
}
for (let i = 0; i < removedLayers.length; i++) {
removedLayers[i].hide();
}
for (let i = 0; i < addedLayers.length; i++) {
addedLayers[i].show();
}
this._handlingClick = false;
this._refocusOnMap();
}
}

View File

@ -0,0 +1,74 @@
import Control from './base';
import * as DOM from '../../util/dom';
import { bindAll } from '../../util/event';
export default class Zoom extends Control {
constructor(cfg) {
super({
position: 'topleft',
zoomInText: '+',
zoomInTitle: 'Zoom in',
zoomOutText: '&#x2212;',
zoomOutTitle: 'Zoom out',
...cfg
});
bindAll([ '_updateDisabled', '_zoomIn', '_zoomOut' ], this);
}
onAdd() {
const zoomName = 'l7-control-zoom';
const container = DOM.create('div', zoomName + ' l7-bar');
this._zoomInButton = this._createButton(this.get('zoomInText'), this.get('zoomInTitle'),
zoomName + '-in', container, this._zoomIn);
this._zoomOutButton = this._createButton(this.get('zoomOutText'), this.get('zoomOutTitle'),
zoomName + '-out', container, this._zoomOut);
this._updateDisabled();
this._scene.on('zoomend', this._updateDisabled);
this._scene.on('zoomchange', this._updateDisabled);
return container;
}
onRemove() {
this._scene.off('zoomend', this._updateDisabled);
this._scene.off('zoomchange', this._updateDisabled);
}
disable() {
this._disabled = true;
this._updateDisabled();
return this;
}
enable() {
this._disabled = false;
this._updateDisabled();
return this;
}
_zoomIn() {
if (!this._disabled && this._scene.getZoom() < this._scene.get('maxZoom')) {
this._scene.zoomIn();
}
}
_zoomOut() {
if (!this._disabled && this._scene.getZoom() > this._scene.get('minZoom')) {
this._scene.zoomOut();
}
}
_createButton(html, tile, className, container, fn) {
const link = DOM.create('a', className, container);
link.innerHTML = html;
link.href = '#';
link.tile = tile;
link.addEventListener('click', fn);
return link;
}
_updateDisabled() {
const scene = this._scene;
const className = 'l7-disabled';
DOM.removeClass(this._zoomInButton, className);
DOM.removeClass(this._zoomOutButton, className);
if (this._disabled || scene.getZoom() === scene.get('minZoom')) {
DOM.addClass(this._zoomOutButton, className);
}
if (this._disabled || scene._zoom === scene.get('maxZoom')) {
DOM.addClass(this._zoomInButton, className);
}
}
}

243
src/component/css/l7.css Normal file
View File

@ -0,0 +1,243 @@
.l7-popup {
position: absolute;
top: 0;
left: 0;
display: -webkit-flex;
display: flex;
will-change: transform;
pointer-events: none;
z-index: 5;
}
.l7-popup-tip {
width: 0;
height: 0;
border: 10px solid transparent;
z-index: 1;
}
.l7-popup-close-button {
position: absolute;
right: 0;
top: 0;
border: 0;
border-radius: 0 3px 0 0;
cursor: pointer;
background-color: transparent;
}
.l7-popup-close-button:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.l7-popup-content {
position: relative;
background: #fff;
border-radius: 3px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
padding: 10px 10px 15px;
pointer-events: auto;
}
/* general toolbar styles */
.l7-bar {
box-shadow: 0 1px 5px rgba(0,0,0,0.65);
border-radius: 4px;
}
.l7-bar a,
.l7-bar a:hover {
background-color: #fff;
border-bottom: 1px solid #ccc;
width: 26px;
height: 26px;
line-height: 26px;
display: block;
text-align: center;
text-decoration: none;
color: black;
}
.l7-bar a,
.l7-control-layers-toggle {
background-position: 50% 50%;
background-repeat: no-repeat;
display: block;
}
.l7-bar a:hover {
background-color: #f4f4f4;
}
.l7-bar a:first-child {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.l7-bar a:last-child {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
border-bottom: none;
}
.l7-bar a.l7-disabled {
cursor: default;
background-color: #f4f4f4;
color: #bbb;
}
/* control positioning */
.l7-control {
position: relative;
z-index: 800;
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}
.l7-top,
.l7-bottom {
position: absolute;
z-index: 1000;
pointer-events: none;
}
.l7-top {
top: 0;
}
.l7-right {
right: 0;
}
.l7-bottom {
bottom: 0;
}
.l7-left {
left: 0;
}
.l7-control {
float: left;
clear: both;
}
.l7-right .l7-control {
float: right;
}
.l7-top .l7-control {
margin-top: 10px;
}
.l7-bottom .l7-control {
margin-bottom: 10px;
}
.l7-left .l7-control {
margin-left: 10px;
}
.l7-right .l7-control {
margin-right: 10px;
}
/* attribution and scale controls */
.l7-control-container .l7-control-attribution {
background: #fff;
background: rgba(255, 255, 255, 0.7);
margin: 0;
}
.l7-control-attribution,
.l7-control-scale-line {
padding: 0 5px;
color: #333;
}
.l7-control-attribution a {
text-decoration: none;
}
.l7-control-attribution a:hover {
text-decoration: underline;
}
.l7-container .l7-control-attribution,
.l7-container .l7-control-scale {
font-size: 11px;
}
.l7-left .l7-control-scale {
margin-left: 5px;
}
.l7-bottom .l7-control-scale {
margin-bottom: 5px;
}
.l7-control-scale-line {
border: 2px solid #777;
border-top: none;
line-height: 1.1;
padding: 2px 5px 1px;
font-size: 11px;
white-space: nowrap;
overflow: hidden;
-moz-box-sizing: border-box;
box-sizing: border-box;
background: #fff;
background: rgba(255, 255, 255, 0.5);
}
.l7-control-scale-line:not(:first-child) {
border-top: 2px solid #777;
border-bottom: none;
margin-top: -2px;
}
.l7-control-scale-line:not(:first-child):not(:last-child) {
border-bottom: 2px solid #777;
}
.l7-touch .l7-control-attribution,
.l7-touch .l7-control-layers,
.l7-touch .l7-bar {
box-shadow: none;
}
.l7-touch .l7-control-layers,
.l7-touch .l7-bar {
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
/* layers control */
.l7-control-layers {
box-shadow: 0 1px 5px rgba(0,0,0,0.4);
background: #fff;
border-radius: 5px;
}
.l7-control-layers-toggle {
background-image: url(images/layers.png);
width: 36px;
height: 36px;
}
.l7-retina .l7-control-layers-toggle {
background-image: url(images/layers-2x.png);
background-size: 26px 26px;
}
.l7-touch .l7-control-layers-toggle {
width: 44px;
height: 44px;
}
.l7-control-layers .l7-control-layers-list,
.l7-control-layers-expanded .l7-control-layers-toggle {
display: none;
}
.l7-control-layers-expanded .l7-control-layers-list {
display: block;
position: relative;
}
.l7-control-layers-expanded {
padding: 6px 10px 6px 6px;
color: #333;
background: #fff;
}
.l7-control-layers-scrollbar {
overflow-y: scroll;
overflow-x: hidden;
padding-right: 5px;
}
.l7-control-layers-selector {
margin-top: 2px;
position: relative;
top: 1px;
}
.l7-control-layers label {
display: block;
}
.l7-control-layers-separator {
height: 0;
border-top: 1px solid #ddd;
margin: 5px -10px 5px -6px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

127
src/component/popup.js Normal file
View File

@ -0,0 +1,127 @@
import Base from '../core/base';
import { bindAll } from '../util/event';
import * as DOM from '../util/dom';
export default class Popup extends Base {
constructor(cfg) {
super({
closeButton: true,
closeOnClick: true,
maxWidth: '240px',
anchor: 'bottom',
...cfg
});
bindAll([ '_update', '_onClickClose', 'remove' ], this);
}
addTo(scene) {
this._scene = scene;
if (this.get('closeOnClick')) {
this._scene.on('click', this._onClickClose);
}
this._scene.on('mapmove', this._update);
this._update();
}
setLnglat(lngLat) {
this.lngLat = lngLat;
if (this._scene) {
this._scene.on('mapmove', this._update);
}
this._update(lngLat);
return this;
}
_update() {
const hasPosition = this.lngLat;
if (!this._scene || !hasPosition || !this._content) { return; }
if (!this._container) {
this._container = this.creatDom('div', 'l7-popup', this._scene.getContainer());
// this._tip = this.creatDom('div', 'l7-popup-tip', this._container);
this._container.appendChild(this._content);
if (this.get('className')) {
this.get('className').split(' ').forEach(name =>
this._container.classList.add(name));
}
}
if (this.get('maxWidth') && this._container.style.maxWidth !== this.get('maxWidth')) {
this._container.style.maxWidth = this.get('maxWidth');
}
this._updatePosition();
}
_updatePosition() {
if (!this._scene) { return; }
const pos = this._scene.lngLatToContainer(this.lngLat);
this._container.style.left = pos.x + 'px';
this._container.style.top = pos.y + 'px';
}
setHTML(html) {
const frag = window.document.createDocumentFragment();
const temp = window.document.createElement('body');
let child;
temp.innerHTML = html;
while (true) { // eslint-disable-line
child = temp.firstChild;
if (!child) break;
frag.appendChild(child);
}
return this.setDOMContent(frag);
}
setText(text) {
return this.setDOMContent(window.document.createTextNode(text));
}
setMaxWidth(maxWidth) {
this.set('maxWidth', maxWidth);
this._update();
return this;
}
setDOMContent(htmlNode) {
this._createContent();
this._content.appendChild(htmlNode);
this._update();
return this;
}
_createContent() {
if (this._content) {
DOM.remove(this._content);
}
this._content = DOM.create('div', 'l7-popup-content', this._container);
if (this.get('closeButton')) {
this._closeButton = DOM.create('button', 'l7-popup-close-button', this._content);
this._closeButton.type = 'button';
this._closeButton.setAttribute('aria-label', 'Close popup');
this._closeButton.innerHTML = '&#215;';
this._closeButton.addEventListener('click', this._onClickClose);
}
}
_onClickClose() {
this.remove();
}
creatDom(tagName, className, container) {
const el = window.document.createElement(tagName);
if (className !== undefined) el.className = className;
if (container) container.appendChild(el);
return el;
}
removeDom(node) {
if (node.parentNode) {
node.parentNode.removeChild(node);
}
}
// 移除popup
remove() {
if (this._content) {
this.removeDom(this._content);
}
if (this._container) {
this.removeDom(this._container);
delete this._container;
}
if (this._scene) {
this._scene.off('mapmove', this._update);
this._scene.off('click', this._onClickClose);
delete this._scene;
}
this.emit('close');
return this;
}
}

View File

@ -0,0 +1,40 @@
import Util from '../../util';
import * as DOM from '../../util/dom';
export default class Control {
constructor(cfg) {
Util.assign(this, cfg);
this._initControlPos();
}
addControl(control) {
control.addTo(this.scene);
}
removeControl(control) {
control.remove();
return this;
}
_initControlPos() {
const corners = this._controlCorners = {};
const l = 'l7-';
const container = this._controlContainer =
DOM.create('div', l + 'control-container', this.scene.getContainer());
function createCorner(vSide, hSide) {
const className = l + vSide + ' ' + l + hSide;
corners[vSide + hSide] = DOM.create('div', className, container);
}
createCorner('top', 'left');
createCorner('top', 'right');
createCorner('bottom', 'left');
createCorner('bottom', 'right');
}
_clearControlPos() {
for (const i in this._controlCorners) {
DOM.remove(this._controlCorners[i]);
}
DOM.remove(this._controlContainer);
delete this._controlCorners;
delete this._controlContainer;
}
}

View File

@ -2,18 +2,35 @@ import Util from '../../util';
export default class EventContoller { export default class EventContoller {
constructor(cfg) { constructor(cfg) {
Util.assign(this, cfg); Util.assign(this, cfg);
this._init();
this.startPoint = { x: -1, y: -1 };
this._selectedId = null;
} }
_init() { _init() {
this.layer.scene.on('pick-' + this.layer.layerId, e => { this.layer.scene.on('pick-' + this.layer.layerId, e => {
let { featureId, point2d, type } = e; let { featureId, point2d, type } = e;
if (featureId < 0 && this._activeIds !== null) { if (type === 'click') {
type = 'mouseleave'; return;
}
if (type === 'mousedown') {
this.startPoint = point2d;
}
if (type === 'mouseup') {
this.endPoint = point2d;
if (this.startPoint.x - this.endPoint.x === 0 && this.startPoint.y - this.endPoint.y === 0) {
type = 'click';
}
} }
this._activeIds = featureId;
// TODO 瓦片图层获取选中数据信息 // TODO 瓦片图层获取选中数据信息
const lnglat = this.layer.scene.containerToLngLat(point2d); const lnglat = this.layer.scene.containerToLngLat(point2d);
const { feature, style } = this.layer.getSelectFeature(featureId, lnglat); let feature = null;
// const style = this.layerData[featureId - 1]; let style = null;
if (featureId !== -999) {
const res = this.layer.getSelectFeature(featureId, lnglat);
feature = res.feature;
style = res.style;
}
const target = { const target = {
featureId, featureId,
feature, feature,
@ -22,9 +39,16 @@ export default class EventContoller {
type, type,
lnglat: { lng: lnglat.lng, lat: lnglat.lat } lnglat: { lng: lnglat.lng, lat: lnglat.lat }
}; };
if (featureId >= 0 || this._activeIds >= 0) { // 拾取到元素,或者离开元素 if (featureId >= 0) { // 拾取到元素,或者离开元素
this.layer.emit(type, target); this.layer.emit(type, target);
this._selectedId = featureId;
} }
if (featureId < 0 && this._selectedId != null) {
type = 'mouseleave';
this.layer.emit(type, target);
this._selectedId = null;
}
this.layer._activeIds = featureId;
}); });
} }

View File

@ -33,7 +33,7 @@ class Picking {
window.addEventListener('resize', this._resizeHandler, false); window.addEventListener('resize', this._resizeHandler, false);
} }
pickdata(event) { pickdata(event) {
const point = { x: event.offsetX, y: event.offsetY, type: event.type }; const point = { x: event.offsetX, y: event.offsetY, type: event.type, _parent: event };
const normalisedPoint = { x: 0, y: 0 }; const normalisedPoint = { x: 0, y: 0 };
normalisedPoint.x = (point.x / this._width) * 2 - 1; normalisedPoint.x = (point.x / this._width) * 2 - 1;
normalisedPoint.y = -(point.y / this._height) * 2 + 1; normalisedPoint.y = -(point.y / this._height) * 2 + 1;
@ -74,6 +74,7 @@ class Picking {
return isVisable; return isVisable;
} }
_pickAllObject(point, normalisedPoint) { _pickAllObject(point, normalisedPoint) {
this.world.children.forEach((object, index) => { this.world.children.forEach((object, index) => {
if (!this._layerIsVisable(object)) { if (!this._layerIsVisable(object)) {
return; return;
@ -81,15 +82,12 @@ class Picking {
this._filterObject(index); this._filterObject(index);
const item = this._pick(point, normalisedPoint, object.name); const item = this._pick(point, normalisedPoint, object.name);
item.type = point.type; item.type = point.type;
item._parent = point._parent;
this._world.emit('pick', item); this._world.emit('pick', item);
this._world.emit('pick-' + object.name, item); this._world.emit('pick-' + object.name, item);
}); });
} }
// _updateRender() {
// this._renderer.render(this._pickingScene, this._camera, this._pickingTexture);
// }
_pick(point, normalisedPoint, layerId) { _pick(point, normalisedPoint, layerId) {
this._update(point); this._update(point);
let id = (this.pixelBuffer[2] * 255 * 255) + (this.pixelBuffer[1] * 255) + (this.pixelBuffer[0]); let id = (this.pixelBuffer[2] * 255 * 255) + (this.pixelBuffer[1] * 255) + (this.pixelBuffer[0]);

View File

@ -8,7 +8,7 @@ export default class Renderer {
} }
initRender() { initRender() {
this.renderer = new THREE.WebGLRenderer({ this.renderer = new THREE.WebGLRenderer({
antialias: true, antialias: false,
alpha: true, alpha: true,
autoClear: false autoClear: false
}); });

View File

@ -77,7 +77,7 @@ export default class Layer extends Base {
world.add(this._object3D); world.add(this._object3D);
this.layerMesh = null; this.layerMesh = null;
this.layerLineMesh = null; this.layerLineMesh = null;
this._initEvents(); // this._initEvents();
} }
/** /**
* 将图层添加加到 Object * 将图层添加加到 Object
@ -269,10 +269,12 @@ export default class Layer extends Base {
} }
hide() { hide() {
this._visible(false); this._visible(false);
this.scene._engine.update();
return this; return this;
} }
show() { show() {
this._visible(true); this._visible(true);
this.scene._engine.update();
return this; return this;
} }
setData(data, cfg) { setData(data, cfg) {
@ -312,16 +314,21 @@ export default class Layer extends Base {
this._setAttrOptions(attrName, attrCfg); this._setAttrOptions(attrName, attrCfg);
} }
_initControllers() { _initControllers() {
const mappingCtr = new Controller.Mapping({ layer: this });
const pickCtr = new Controller.Picking({ layer: this }); const pickCtr = new Controller.Picking({ layer: this });
const interactionCtr = new Controller.Interaction({ layer: this }); const interactionCtr = new Controller.Interaction({ layer: this });
this.set('mappingController', mappingCtr); const eventCtr = new Controller.Event({ layer: this });
this.set('pickingController', pickCtr); this.set('pickingController', pickCtr);
this.set('interacionController', interactionCtr); this.set('interacionController', interactionCtr);
this.set('eventController', eventCtr);
}
_mapping() {
const mappingCtr = new Controller.Mapping({ layer: this });
this.set('mappingController', mappingCtr);
} }
render() { render() {
if (this.get('layerType') === 'tile') { if (this.get('layerType') === 'tile') {
this._initControllers();
this._initInteraction();
this.scene.style.update(this._attrs); this.scene.style.update(this._attrs);
return this; return this;
} }
@ -332,16 +339,13 @@ export default class Layer extends Base {
// 重绘 度量, 映射,顶点构建 // 重绘 度量, 映射,顶点构建
repaint() { repaint() {
this.set('scales', {}); this.set('scales', {});
const mappingCtr = new Controller.Mapping({ layer: this }); this._mapping();
this.set('mappingController', mappingCtr);
// this._initAttrs();
// this._mapping();
this.redraw(); this.redraw();
} }
// 初始化图层 // 初始化图层
init() { init() {
this._initControllers(); this._initControllers();
// this._initAttrs(); this._mapping();
this._updateDraw(); this._updateDraw();
} }
_initInteraction() { _initInteraction() {
@ -479,7 +483,6 @@ export default class Layer extends Base {
this.scene.on('pick-' + this.layerId, e => { this.scene.on('pick-' + this.layerId, e => {
let { featureId, point2d, type } = e; let { featureId, point2d, type } = e;
// TODO 瓦片图层获取选中数据信息 // TODO 瓦片图层获取选中数据信息
const lnglat = this.scene.containerToLngLat(point2d); const lnglat = this.scene.containerToLngLat(point2d);
let feature = null; let feature = null;
let style = null; let style = null;
@ -507,8 +510,16 @@ export default class Layer extends Base {
}); });
} }
getSelectFeature(featureId) { getSelectFeature(featureId, lnglat) {
const feature = this.layerSource.getSelectFeature(featureId); // return {};
if (this.get('layerType') === 'tile') {
const sourceCache = this.getSourceCache(this.get('sourceOption').id);
const feature = sourceCache.getSelectFeature(featureId, this.layerId, lnglat);
return {
feature
};
}
const feature = this.layerSource && this.layerSource.getSelectFeature(featureId) || {};
const style = this.layerData[featureId - 1]; const style = this.layerData[featureId - 1];
return { return {
feature, feature,
@ -681,5 +692,10 @@ export default class Layer extends Base {
afterRender() { afterRender() {
} }
// tileLayer
getSourceCache(id) {
return this.scene.style.getSource(id);
}
} }

View File

@ -9,6 +9,7 @@ import Global from '../global';
import { getInteraction } from '../interaction/index'; import { getInteraction } from '../interaction/index';
import { compileBuiltinModules } from '../geom/shader'; import { compileBuiltinModules } from '../geom/shader';
import Style from './style'; import Style from './style';
import Control from './controller/control';
import { epsg3857 } from '@antv/geo-coord/lib/geo/crs/crs-epsg3857'; import { epsg3857 } from '@antv/geo-coord/lib/geo/crs/crs-epsg3857';
export default class Scene extends Base { export default class Scene extends Base {
getDefaultCfg() { getDefaultCfg() {
@ -18,6 +19,7 @@ export default class Scene extends Base {
super(cfg); super(cfg);
this._initMap(); this._initMap();
this.crs = epsg3857; this.crs = epsg3857;
this._initContoller();
// this._initAttribution(); // 暂时取消,后面作为组件去加载 // this._initAttribution(); // 暂时取消,后面作为组件去加载
this.addImage(); this.addImage();
this.fontAtlasManager = new FontAtlasManager(); this.fontAtlasManager = new FontAtlasManager();
@ -31,6 +33,10 @@ export default class Scene extends Base {
// this._engine.run(); // this._engine.run();
compileBuiltinModules(); compileBuiltinModules();
} }
_initContoller() {
const controlCtr = new Control({ scene: this });
this.set('controlController', controlCtr);
}
// 为pickup场景添加 object 对象 // 为pickup场景添加 object 对象
addPickMesh(object) { addPickMesh(object) {
this._engine._picking.add(object); this._engine._picking.add(object);
@ -93,8 +99,21 @@ export default class Scene extends Base {
getLayers() { getLayers() {
return this._layers; return this._layers;
} }
getLayer(id) {
let res = false;
this._layers.forEach(layer => {
if (layer.layerId === id) {
res = layer;
return;
}
});
return res;
}
_addLayer() { _addLayer() {
}
getContainer() {
return this._container;
} }
_registEvents() { _registEvents() {
const events = [ const events = [
@ -109,14 +128,15 @@ export default class Scene extends Base {
'dblclick' 'dblclick'
]; ];
events.forEach(event => { events.forEach(event => {
this._container.addEventListener(event, e => { this._container.addEventListener(event, e => {
// 要素拾取 // 要素拾取
if (e.target.nodeName !== 'CANVAS') return;
e.pixel || (e.pixel = e.point); e.pixel || (e.pixel = e.point);
requestAnimationFrame(() => { requestAnimationFrame(() => {
this._engine._picking.pickdata(e); this._engine._picking.pickdata(e);
}); });
}, false); }, true);
}); });
} }
removeLayer(layer) { removeLayer(layer) {
@ -153,5 +173,12 @@ export default class Scene extends Base {
this.map.off('mapmove', this._updateRender); this.map.off('mapmove', this._updateRender);
this.map.off('camerachange', this._updateRender); this.map.off('camerachange', this._updateRender);
} }
// control
addControl(ctr) {
this.get('controlController').addControl(ctr);
return this;
}
removeControl(ctr) {
this.get('controlController').removeControl(ctr);
}
} }

View File

@ -3,6 +3,7 @@ import WorkerPool from '../worker/worker_pool';
import throttle from '../util/throttle'; import throttle from '../util/throttle';
import SourceCache from '../source/source_cache'; import SourceCache from '../source/source_cache';
import WorkerController from '../worker/worker_controller'; import WorkerController from '../worker/worker_controller';
const omitOption = [ 'mappingController', 'interacionController', 'pickingController', 'interactions', 'eventController' ];
// 统一管理所有的Source // 统一管理所有的Source
// 统一管理地图样式 // 统一管理地图样式
export default class Style extends Base { export default class Style extends Base {
@ -34,8 +35,15 @@ export default class Style extends Base {
// 设置 // 设置
_addTileStyle(layerCfg) { _addTileStyle(layerCfg) {
const layerid = layerCfg.layerId; const layerid = layerCfg.layerId;
this.layerStyles[layerid] = layerCfg; const newLayerCfg = {};
for (const key in layerCfg) { // 过滤不可传递对象
if (omitOption.indexOf(key) === -1) {
newLayerCfg[key] = layerCfg[key];
}
}
this.layerStyles[layerid] = newLayerCfg;
this._layerStyleGroupBySourceID(); this._layerStyleGroupBySourceID();
this.WorkerController.broadcast('setLayers', this.layerStyles); this.WorkerController.broadcast('setLayers', this.layerStyles);
// TODO 更新 style // TODO 更新 style

View File

@ -11,8 +11,8 @@ const Global = {
mapType: 'AMAP', mapType: 'AMAP',
zoom: 5, zoom: 5,
center: [ 107.622, 39.266 ], center: [ 107.622, 39.266 ],
minZoom: 0, minZoom: 3,
maxZoom: 22, maxZoom: 18,
pitch: 0, pitch: 0,
hash: false hash: false
}, },

View File

@ -1,3 +1,4 @@
import './component/css/l7.css';
import Scene from './core/scene'; import Scene from './core/scene';
import Global from './global'; import Global from './global';
import Source from './core/source'; import Source from './core/source';
@ -5,6 +6,8 @@ import TileSource from './source/tile_source';
import { registerParser, registerTransform } from './source'; import { registerParser, registerTransform } from './source';
import { registerInteraction, getInteraction } from './interaction'; import { registerInteraction, getInteraction } from './interaction';
import { registerLayer } from './layer'; import { registerLayer } from './layer';
import Popup from './component/popup';
import * as Control from './component/control';
const version = Global.version; const version = Global.version;
const exported = { const exported = {
version, version,
@ -15,6 +18,8 @@ const exported = {
registerTransform, registerTransform,
registerLayer, registerLayer,
registerInteraction, registerInteraction,
getInteraction getInteraction,
Popup,
Control
}; };
export default exported; export default exported;

View File

@ -23,10 +23,16 @@ export default class VectorTileMesh {
const tileMesh = this._tileMaskMesh(); const tileMesh = this._tileMaskMesh();
// this._object3D.add(tileMesh); // this._object3D.add(tileMesh);
this.maskScene.add(tileMesh); this.maskScene.add(tileMesh);
this._bufferData = data;
} }
_init(data) { _init(data) {
this._createMesh(data); this._createMesh(data);
} }
getFeatureIndex(id) {
return this._bufferData.featureKey.indexOf(id);
}
_createMesh(data) { _createMesh(data) {
this.mesh = getRender(this.layer.get('type'), data.shape)(null, this.layer, data.buffer); this.mesh = getRender(this.layer.get('type'), data.shape)(null, this.layer, data.buffer);
if (this.mesh.type !== 'composer') { // 热力图的情况 if (this.mesh.type !== 'composer') { // 热力图的情况
@ -121,6 +127,7 @@ export default class VectorTileMesh {
const n = Math.PI - 2 * Math.PI * y / Math.pow(2, z); const n = Math.PI - 2 * Math.PI * y / Math.pow(2, z);
return r2d * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n))); return r2d * Math.atan(0.5 * (Math.exp(n) - Math.exp(-n)));
} }
destroy() { destroy() {
destoryObject(this._object3D); destoryObject(this._object3D);
destoryObject(this.maskScene); destoryObject(this.maskScene);

View File

@ -170,7 +170,7 @@ export default class GaodeMap extends Base {
return map.pixelToLngLat(ll); return map.pixelToLngLat(ll);
}; };
scene.lngLatToPixel = lnglat => { scene.lngLatToPixel = lnglat => {
return map.lngLatToPixel(new AMap.LngLat(lnglat[0], lnglat[1])); return map.lnglatToPixel(new AMap.LngLat(lnglat[0], lnglat[1]));
}; };
scene.setMapStyle = style => { scene.setMapStyle = style => {
return map.setMapStyle(style); return map.setMapStyle(style);
@ -184,5 +184,9 @@ export default class GaodeMap extends Base {
const ll = new AMap.Pixel(pixel.x, pixel.y); const ll = new AMap.Pixel(pixel.x, pixel.y);
return map.containerToLngLat(ll); return map.containerToLngLat(ll);
}; };
scene.lngLatToContainer = lnglat => {
const ll = new AMap.LngLat(lnglat[0], lnglat[1]);
return map.lngLatToContainer(ll);
};
} }
} }

View File

@ -3,7 +3,7 @@ const Extent = 4096;
export default function vector(data, cfg) { export default function vector(data, cfg) {
const tile = cfg.tile; const tile = cfg.tile;
const resultdata = []; const resultdata = [];
const featureKeys = {}; const featureKeys = new Int32Array(data.length);
const x0 = Extent * tile[0]; const x0 = Extent * tile[0];
const y0 = Extent * tile[1]; const y0 = Extent * tile[1];
function covertP20(points) { function covertP20(points) {
@ -13,29 +13,24 @@ export default function vector(data, cfg) {
return [ x1, -y2, 0 ]; return [ x1, -y2, 0 ];
}); });
} }
let index = 0;
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
const feature = data.feature(i); const feature = data.feature(i);
const coords = feature.loadGeometry(); const coords = feature.loadGeometry();
const properties = feature.properties; const properties = feature.properties;
let id = i + 1; let id = i + 1;
if (cfg.idField && properties[cfg.idField]) { if (feature.id || (cfg.idField && properties[cfg.idField])) {
const value = properties[cfg.idField]; const value = feature.id || properties[cfg.idField];
id = djb2hash(value) % 1000019; id = djb2hash(value) % 1000019;
featureKeys[id] = { featureKeys[i] = id;
index,
idField: value
};
} }
const geocoords = classifyRings(coords); const geocoords = classifyRings(coords);
for (let j = 0; j < geocoords.length; j++) { for (let j = 0; j < geocoords.length; j++) {
const geo = geocoords[j].map(coord => { const geo = geocoords[j].map(coord => {
return covertP20(coord); return covertP20(coord);
}); });
index++;
resultdata.push({ resultdata.push({
...properties, ...properties,
_id: feature.id || id, _id: id,
coordinates: geo coordinates: geo
}); });
} }
@ -46,6 +41,7 @@ export default function vector(data, cfg) {
featureKeys featureKeys
}; };
} }
function signedArea(ring) { function signedArea(ring) {
let sum = 0; let sum = 0;

View File

@ -1,8 +1,11 @@
import Base from '../core/base'; import Base from '../core/base';
import TileCache from '../layer/tile/tile_cache'; import TileCache from '../layer/tile/tile_cache';
import VectorTileSource from './vector_tile_source'; import VectorTileSource from './vector_tile_source';
import PBF from 'pbf';
import * as VectorParser from '@mapbox/vector-tile';
import { toLngLat, Bounds, Point } from '@antv/geo-coord'; import { toLngLat, Bounds, Point } from '@antv/geo-coord';
import VectorTileMesh from '../layer/tile/vector_tile_mesh'; import VectorTileMesh from '../layer/tile/vector_tile_mesh';
import { epsg3857 } from '@antv/geo-coord/lib/geo/crs/crs-epsg3857';
// 统一管理 source 添加,管理,更新 // 统一管理 source 添加,管理,更新
export default class SouceCache extends Base { export default class SouceCache extends Base {
constructor(scene, cfg) { constructor(scene, cfg) {
@ -18,10 +21,10 @@ export default class SouceCache extends Base {
this.scene = scene; this.scene = scene;
// TODO 销毁函数 // TODO 销毁函数
this._tileCache = new TileCache(this.get('cacheLimit'), this._destroyTile.bind(this)); this._tileCache = new TileCache(this.get('cacheLimit'), this._destroyTile.bind(this));
this._crs = epsg3857;
this.layers = this.scene.getLayers(); this.layers = this.scene.getLayers();
this._source = new VectorTileSource(cfg, this.scene.style.WorkerController); this._source = new VectorTileSource(cfg, this.scene.style.WorkerController);
this.layersTiles = {}; // 存储当前source所有layer的瓦片 this.layersTiles = {}; // 存储当前source所有layer的瓦片
// this._tiles = new THREE.Object3D();
} }
getLayerById(id) { getLayerById(id) {
const layers = this.scene.getLayers(); const layers = this.scene.getLayers();
@ -31,17 +34,14 @@ export default class SouceCache extends Base {
} }
} }
} }
/** /**
* 移除视野外的瓦片计算新增的瓦片数据 * 移除视野外的瓦片计算新增的瓦片数据
* @param {*}tileMap 瓦片列表 * @param {*}tileMap 瓦片列表
*/ */
update() { update() {
// if (!layercfg && this.layercfg) return;
// this._layercfg = layercfg;
this._calculateTileIDs(); this._calculateTileIDs();
// this.updateList = this._getNewTiles(this._tileMap);// 计算新增瓦片
// this._pruneTiles();
for (let i = 0; i < this.updateTileList.length; i++) { for (let i = 0; i < this.updateTileList.length; i++) {
// 瓦片相关参数 // 瓦片相关参数
const tileId = this.updateTileList[i].join('_'); const tileId = this.updateTileList[i].join('_');
@ -51,10 +51,11 @@ export default class SouceCache extends Base {
if (tiles !== undefined) { if (tiles !== undefined) {
tileinfo.active = true; tileinfo.active = true;
tileinfo.loaded = true; tileinfo.loaded = true;
for (const layerId in tiles) { for (const layerId in tiles.mesh) {
const layer = this.getLayerById(layerId); const layer = this.getLayerById(layerId);
const tileMesh = tiles[layerId]; const tileMesh = tiles.mesh[layerId];
layer.tiles.add(tileMesh.getMesh()); layer.tiles.add(tileMesh.getMesh());
this._addPickMesh(layer, tileMesh.getMesh());
this.scene._engine.update(); this.scene._engine.update();
} }
this._pruneTiles(); this._pruneTiles();
@ -73,17 +74,50 @@ export default class SouceCache extends Base {
_renderTile(tileinfo, data) { _renderTile(tileinfo, data) {
const tileId = tileinfo.id; const tileId = tileinfo.id;
const tiles = {}; const tiles = {
for (const layerId in data) { rawData: data.rawTileData,
mesh: {}
};
for (const layerId in data.buffer) {
const layer = this.getLayerById(layerId); const layer = this.getLayerById(layerId);
const tileMesh = new VectorTileMesh(layer, data[layerId]); const tileMesh = new VectorTileMesh(layer, data.buffer[layerId]);
tiles[layerId] = tileMesh; tiles.mesh[layerId] = tileMesh;
layer.tiles.add(tileMesh.getMesh()); layer.tiles.add(tileMesh.getMesh());
this._addPickMesh(layer, tileMesh.getMesh());
this.scene._engine.update(); this.scene._engine.update();
} }
this._tileCache.setTile(tiles, tileId); this._tileCache.setTile(tiles, tileId);
} }
getSelectFeature(featureId, layerId, lnglat) {
const zoom = this.tileZoom;
const tilePoint = this._crs.lngLatToPoint(toLngLat(lnglat.lng, lnglat.lat), zoom);
const tileXY = tilePoint.divideBy(256).floor();
const tile = this._getParentTile(tileXY.x, tileXY.y, zoom, zoom - 3);
const layer = this.getLayerById(layerId);
const sourceLayer = layer.get('sourceOption').parser.sourceLayer;
const featureIndex = tile.mesh[layerId].getFeatureIndex(featureId);
const feature = this._getVectorFeature(tile.rawData, sourceLayer, featureIndex);
return feature ? feature.toGeoJSON(tileXY.x, tileXY.y, zoom) : { };
}
_getParentTile(x, y, z, minZoom) {
if (z < minZoom) return null;
const key = [ x, y, z ].join('_');
const tile = this._tileCache.getTile(key);
if (!tile) {
return this._getParentTile(Math.floor(x / 2), Math.floor(y / 2), z - 1);
}
return tile;
}
_getVectorFeature(rawTile, sourceLayer, featureIndex) {
const vectorTile = new VectorParser.VectorTile(new PBF(rawTile));
if (featureIndex < 0) {
return;
}
return vectorTile.layers[sourceLayer].feature(featureIndex);
}
// 计算视野内的瓦片坐标 // 计算视野内的瓦片坐标
_calculateTileIDs() { _calculateTileIDs() {
this._tileMap = {}; this._tileMap = {};
@ -265,6 +299,16 @@ export default class SouceCache extends Base {
} }
} }
} }
}
// 地图拾取
_addPickMesh(layer, meshObj) {
if (this.type === 'image') {
return;
}
const pickCtr = layer.get('pickingController');
const mesh = meshObj.children[0];
mesh.name = meshObj.name;
pickCtr.addPickMesh(mesh);
} }
_removeOutTiles() { _removeOutTiles() {
// 移除视野外的tile // 移除视野外的tile
@ -286,6 +330,8 @@ export default class SouceCache extends Base {
const key = tile.name; const key = tile.name;
if (!this.tileList[key]) { if (!this.tileList[key]) {
layers[i].tiles.remove(tile); layers[i].tiles.remove(tile);
const pickCtr = layers[i].get('pickingController');
pickCtr && pickCtr.removePickMeshByName(key);
} }
}); });
} }
@ -295,5 +341,4 @@ export default class SouceCache extends Base {
this._unloadTile(key); this._unloadTile(key);
} }
// 移除视野外的tile // 移除视野外的tile
} }

View File

@ -41,14 +41,14 @@ export default class VectorTileWorkerSource {
this.loaded[uid] = workerTile; this.loaded[uid] = workerTile;
return callback(err); return callback(err);
} }
// const rawTileData = response.rawData;
workerTile.vectorTile = response.vectorTile; workerTile.vectorTile = response.vectorTile;
const rawTileData = response.rawData;
workerTile.parse(response.vectorTile, this.layerStyle, this.actor, (err, result) => { workerTile.parse(response.vectorTile, this.layerStyle, this.actor, (err, result) => {
if (err || !result) return callback(err); if (err || !result) return callback(err);
// Transferring a copy of rawTileData because the worker needs to retain its copy. // Transferring a copy of rawTileData because the worker needs to retain its copy.
callback(null, { callback(null, {
// rawTileData: rawTileData.slice(0), buffer: result,
...result rawTileData: rawTileData.slice(0)
}); });
}); });

View File

@ -15,6 +15,7 @@ export function BKDRHash(str) {
return hash; return hash;
} }
export function djb2hash(str) { export function djb2hash(str) {
str = str.toString();
let hash = 5381, let hash = 5381,
i = str.length; i = str.length;

80
src/util/dom.js Normal file
View File

@ -0,0 +1,80 @@
import * as Util from './util';
export function create(tagName, className, container) {
const el = document.createElement(tagName);
el.className = className || '';
if (container) {
container.appendChild(el);
}
return el;
}
// @function remove(el: HTMLElement)
// Removes `el` from its parent element
export function remove(el) {
const parent = el.parentNode;
if (parent) {
parent.removeChild(el);
}
}
// @function addClass(el: HTMLElement, name: String)
// Adds `name` to the element's class attribute.
export function addClass(el, name) {
if (el.classList !== undefined) {
const classes = Util.splitWords(name);
for (let i = 0, len = classes.length; i < len; i++) {
el.classList.add(classes[i]);
}
} else if (!hasClass(el, name)) {
const className = getClass(el);
setClass(el, (className ? className + ' ' : '') + name);
}
}
// @function removeClass(el: HTMLElement, name: String)
// Removes `name` from the element's class attribute.
export function removeClass(el, name) {
if (el.classList !== undefined) {
el.classList.remove(name);
} else {
setClass(el, Util.trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
}
}
// @function hasClass(el: HTMLElement, name: String): Boolean
// Returns `true` if the element's class attribute contains `name`.
export function hasClass(el, name) {
if (el.classList !== undefined) {
return el.classList.contains(name);
}
const className = getClass(el);
return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
}
// @function setClass(el: HTMLElement, name: String)
// Sets the element's class.
export function setClass(el, name) {
if (el.className.baseVal === undefined) {
el.className = name;
} else {
// in case of SVG element
el.className.baseVal = name;
}
}
// @function getClass(el: HTMLElement): String
// Returns the element's class.
export function getClass(el) {
// Check if the element is an SVGElementInstance and use the correspondingElement instead
// (Required for linked SVG elements in IE11.)
if (el.correspondingElement) {
el = el.correspondingElement;
}
return el.className.baseVal === undefined ? el.className : el.className.baseVal;
}
export function empty(el) {
while (el.firstChild) {
el.removeChild(el.firstChild);
}
}

6
src/util/event.js Normal file
View File

@ -0,0 +1,6 @@
export function bindAll(fns, context) {
fns.forEach(fn => {
if (!context[fn]) { return; }
context[fn] = context[fn].bind(context);
});
}

9
src/util/util.js Normal file
View File

@ -0,0 +1,9 @@
export function trim(str) {
return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
}
// @function splitWords(str: String): String[]
// Trims and splits the string on whitespace and returns the array of parts.
export function splitWords(str) {
return trim(str).split(/\s+/);
}

View File

@ -1,10 +1,5 @@
import { serialize } from './worker_transform'; import { serialize } from './worker_transform';
function bindAll(fns, context) { import { bindAll } from '../util/event';
fns.forEach(fn => {
if (!context[fn]) { return; }
context[fn] = context[fn].bind(context);
});
}
export default class Actor { export default class Actor {
constructor(target, parent, mapId) { constructor(target, parent, mapId) {

View File

@ -2,6 +2,7 @@ import TileMapping from '../core/controller/tile_mapping';
import { getBuffer } from '../geom/buffer/index'; import { getBuffer } from '../geom/buffer/index';
import Source from '../core/source'; import Source from '../core/source';
import Global from '../global'; import Global from '../global';
import { feature } from '_@turf_helpers@6.1.4@@turf/helpers';
const { pointShape } = Global; const { pointShape } = Global;
export default class WorkerTile { export default class WorkerTile {
@ -25,16 +26,15 @@ export default class WorkerTile {
const style = sourceStyle[sourcelayer][0]; const style = sourceStyle[sourcelayer][0];
style.sourceOption.parser.type = 'vector'; style.sourceOption.parser.type = 'vector';
style.sourceOption.parser.tile = tile; style.sourceOption.parser.tile = tile;
const tileSource2 = new Source({ const tileSource = new Source({
...style.sourceOption, ...style.sourceOption,
mapType: style.mapType, mapType: style.mapType,
projected: true, projected: true,
data: data.layers[sourcelayer] data: data.layers[sourcelayer]
}); });
for (let i = 0; i < sourceStyle[sourcelayer].length; i++) { for (let i = 0; i < sourceStyle[sourcelayer].length; i++) {
const style = sourceStyle[sourcelayer][i]; const style = sourceStyle[sourcelayer][i];
const tileMapping = new TileMapping(tileSource2, style); const tileMapping = new TileMapping(tileSource, style);
if (style.type === 'point') { if (style.type === 'point') {
style.shape = this._getPointShape(tileMapping); style.shape = this._getPointShape(tileMapping);
} }
@ -53,11 +53,13 @@ export default class WorkerTile {
shape: style.shape, shape: style.shape,
layerId: style.layerId, layerId: style.layerId,
sourcelayer, sourcelayer,
tileId: this.tileID tileId: this.tileID,
featureKey: tileSource.data.featureKeys.slice(0)
}; };
} }
} }
this.status = 'done'; this.status = 'done';
callback(null, { ...sourceLayerData }); callback(null, { ...sourceLayerData });
} }
_layerStyleGroupBySourceID(layerStyles) { _layerStyleGroupBySourceID(layerStyles) {