feat: 栅格rampcolor 支持cat、quantize、custom 着色方式 (#1554)

* fix: circle meter size && remove unuse code file

* docs: add demo

* feat: 新增attribute diff 校验

* fix: lint error

* feat: 增强 rampcolor 类型

* fix: rampcolor domain

* docs: rampcolor 优化

* fix: 纬度范围大于85 的情况

* fix: rampcolor quantize

* docs: 更新demo
This commit is contained in:
@thinkinggis 2022-12-29 16:06:04 +08:00 committed by GitHub
parent 5f489e55c8
commit ba7c9a0c1d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
43 changed files with 943 additions and 5327 deletions

View File

@ -0,0 +1,98 @@
import type { ChoroplethLayerProps,IconImageLayerProps } from '@antv/larkmap';
import { ChoroplethLayer, LarkMap,LegendRamp,CustomControl,IconImageLayer} from '@antv/larkmap';
import React, { useEffect, useState } from 'react';
;
const layerOptions: Omit<ChoroplethLayerProps, 'source'> = {
autoFit: true,
fillColor: {
field: '达峰进度条',
value: [
'#fee5d9',
'#fc9272',
'#fb6a4a',
'#de2d26',
'#a50f15',
],
scale: {
type: 'quantize',
domain: [0, 100],
unknown: '#f7f4f9',
}
},
opacity: 1,
strokeColor: '#ddd',
lineWidth: 1,
state: {
active: { strokeColor: 'green', lineWidth: 1.5, lineOpacity: 0.8 },
select: { strokeColor: 'red', lineWidth: 1.5, lineOpacity: 0.8 },
},
label: {
field: 'name',
visible: false,
style: {
textAllowOverlap: true,
fill: '#333', fontSize: 10, stroke: '#aaa', strokeWidth: 1 },
},
};
export default () => {
const [update,setUpdate] = useState<number>()
const [options, setOptions] = useState(layerOptions);
const [source, setSource] = useState({
data: { type: 'FeatureCollection', features: [] },
parser: {
type: 'json',
geometry: 'geometry',
}
});
const [labelsource, setLabelsource] = useState({
data: { type: 'FeatureCollection', features: [] },
parser: {
type: 'json',
geometry: 'centroid',
}
});
useEffect(() => {
fetch('https://mdn.alipayobjects.com/afts/file/A*7HqFT7he7KoAAAAAAAAAAAAADrd2AQ/12.20%20%E5%90%84%E7%9C%81%E4%BB%BD%E9%A6%96%E8%BD%AE%E6%84%9F%E6%9F%93%E9%AB%98%E5%B3%B0%E6%9C%9F%E9%A2%84%E6%B5%8B.json')
.then((response) => response.json())
.then((data: any) => {
setSource((prevState) => ({ ...prevState, data }));
setLabelsource((prevState) => ({ ...prevState, data }))
});
setInterval(()=>{
setUpdate(Math.random())
},2000)
}, []);
return (
<LarkMap mapType="Gaode" style={{ height: '70vh' }}>
<ChoroplethLayer {...options} source={source}
onDataUpdate={()=>{
console.log('onDataUpdate')
}} />
<CustomControl
position="bottomright"
className="custom-control-class"
style={{ background: '#fff', borderRadius: 4, overflow: 'hidden', padding: 16 }}
>
<h3></h3>
<LegendRamp
lableUnit="%"
labels={[ 0,20, 40, 60, 80, 100]}
colors={[
'#fee5d9',
'#fc9272',
'#fb6a4a',
'#de2d26',
'#a50f15',
]}
barWidth={300}
/>
</CustomControl>
</LarkMap>
);
};

View File

@ -0,0 +1,2 @@
### 区域图
<code src="./demos/polygon.tsx"></code>

File diff suppressed because it is too large Load Diff

View File

@ -1,2 +1,2 @@
### polygon
### select
<code src="./demos/select.tsx"></code>

View File

@ -54,13 +54,9 @@ export default () => {
rampColors: {
colors: [
'#FF4818',
'#F7B74A',
'#FFF598',
'#91EABC',
'#2EA9A1',
'#206C7C',
],
weights: [0.1, 0.1, 0.1, 0.1, 0.1, 0.5],
positions: [0., 1.0],
},
});

View File

@ -0,0 +1,2 @@
### 疫情达峰
<code src="./map.tsx"></code>

View File

@ -0,0 +1,112 @@
import { PolygonLayer, LineLayer, PointLayer, Scene, Source } from '@antv/l7';
import { GaodeMap } from '@antv/l7-maps';
import React, { useEffect } from 'react';
export default () => {
useEffect(() => {
fetch(
'https://mdn.alipayobjects.com/afts/file/A*7HqFT7he7KoAAAAAAAAAAAAADrd2AQ/12.20%20%E5%90%84%E7%9C%81%E4%BB%BD%E9%A6%96%E8%BD%AE%E6%84%9F%E6%9F%93%E9%AB%98%E5%B3%B0%E6%9C%9F%E9%A2%84%E6%B5%8B.json',
)
.then((res) => res.json())
.then((data) => {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
pitch: 0,
style: 'dark',
center: [112, 37.8],
zoom: 3,
}),
});
const chinaSource = new Source(data, {
parser: {
type: 'json',
geometry: 'geometry',
},
});
const layer = new PolygonLayer({
autoFit: true,
})
.source(chinaSource)
.scale('达峰进度条', {
type: 'quantize',
domain: [0, 100],
unknown: '#f7f4f9',
})
.shape('fill')
.color('达峰进度条', [
'#fee5d9',
'#fc9272',
'#fb6a4a',
'#de2d26',
'#a50f15',
])
.style({
opacity: 1,
});
const linelayer = new LineLayer({})
.source(chinaSource)
.shape('line')
.color('#ddd')
.style({
opacity: 1,
});
layer.on('inited', () => {
console.log(layer.getLegend('color'));
});
const pointSource = new Source(data, {
parser: {
type: 'json',
geometry: 'center',
},
});
const nameLayer = new PointLayer()
.source(pointSource)
.size(12)
.shape('name', 'text')
.color('#525252')
.style({
textAnchor: 'top', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
textOffset: [0, 0], // 文本相对锚点的偏移量 [水平, 垂直]
// spacing: 2, // 字符间距
// padding: [ 1, 1 ], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
stroke: '#fff', // 描边颜色
strokeWidth: 1, // 描边宽度
strokeOpacity: 1.0,
});
const textLayer = new PointLayer()
.source(pointSource)
.size(14)
.shape('达峰进度条', 'text')
.color('#e7298a')
.style({
textAnchor: 'bottom', // 文本相对锚点的位置 center|left|right|top|bottom|top-left
textOffset: [0, -20], // 文本相对锚点的偏移量 [水平, 垂直]
// spacing: 2, // 字符间距
// padding: [ 1, 1 ], // 文本包围盒 padding [水平,垂直],影响碰撞检测结果,避免相邻文本靠的太近
stroke: '#fff', // 描边颜色
strokeWidth: 2, // 描边宽度
strokeOpacity: 1.0,
fontWeight: 800,
textAllowOverlap: true,
});
scene.addLayer(layer);
scene.addLayer(linelayer);
scene.addLayer(nameLayer);
scene.addLayer(textLayer);
});
}, []);
return (
<div
id="map"
style={{
height: '100vh',
position: 'relative',
}}
/>
);
};

View File

@ -42,7 +42,7 @@ export default () => {
},
})
.style({
opacity: 0.8,
opacity: 1.0,
clampLow: false,
clampHigh: false,
domain: [100, 8000],

View File

@ -59,10 +59,12 @@ export default () => {
.style({
clampLow: false,
clampHigh: false,
domain: [ 0, 90 ],
domain: [ 1, 90 ],
nodataValue: 0,
rampColors: {
colors: [ 'rgba(92,58,16,0)', 'rgba(92,58,16,0)', '#fabd08', '#f1e93f', '#f1ff8f', '#fcfff7' ],
type:'quantize',
colors:['#1b9e77','#d95f02','#7570b3','#e7298a','#66a61e','#e6ab02'],
// colors: [ 'rgba(92,58,16,0)', 'rgba(92,58,16,0)', '#fabd08', '#f1e93f', '#f1ff8f', '#fcfff7' ],
positions: [ 0, 0.05, 0.1, 0.25, 0.5, 1.0 ]
}
});

View File

@ -1,7 +1,7 @@
// @ts-ignore
import { RasterLayer, Scene } from '@antv/l7';
// @ts-ignore
import { GaodeMap } from '@antv/l7-maps';
import { GaodeMap,Map } from '@antv/l7-maps';
import React, { useEffect } from 'react';
import * as GeoTIFF from 'geotiff';
@ -17,7 +17,7 @@ export default () => {
useEffect(() => {
const scene = new Scene({
id: 'map',
map: new GaodeMap({
map: new Map({
center: [121.268, 30.3628],
zoom: 3,
}),
@ -42,20 +42,14 @@ export default () => {
},
})
.style({
opacity: 0.8,
opacity: 1,
clampLow: false,
clampHigh: false,
domain: [100, 8000],
domain: [0, 10000],
rampColors: {
colors: [
'#FF4818',
'#F7B74A',
'#FFF598',
'#91EABC',
'#2EA9A1',
'#206C7C',
].reverse(),
positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0],
type:'custom',
colors: ['#b2182b','#d6604d','#f4a582','#fddbc7','#f7f7f7','#d1e5f0','#92c5de','#4393c3','#2166ac'],
positions: [0, 50, 200, 500, 2000, 3000, 4000, 5000, 8000,10000],
},
});

View File

@ -2,59 +2,36 @@ import { RasterLayer, Scene, Source } from '@antv/l7';
import { Map } from '@antv/l7-maps';
import React, { useEffect } from 'react';
import * as GeoTIFF from 'geotiff';
// https://gee-community-catalog.org/projects/esrilc2020/
const colorList = [
'#419bdf', // Water
'#419bdf',
'#397d49', // Tree
'#397d49',
'#358221', // Tree
'#88b053', // Grass
'#88b053',
'#7a87c6', // vegetation
'#7a87c6',
'#e49635', // Crops
'#e49635',
'#dfc35a', // shrub
'#dfc35a',
'#c4281b', // Built Area
'#c4281b',
'#a59b8f', // Bare ground
'#a59b8f',
'#ED022A', // Built Area
'#a8ebff', // Snow
'#a8ebff',
'#616161', // Clouds
'#616161',
'#EDE9E4', // Bare ground
'#F2FAFF', // Snow
'#C8C8C8', // Clouds
];
const positions = [
0.0,
0.1,
0.1,
0.2,
0.2,
0.3,
0.3,
0.4,
0.4,
0.5,
0.5,
0.6,
0.6,
0.7,
0.7,
0.8,
0.8,
0.9,
0.9,
1.0,
1,2,3,4,5,6,7,8,9,10,11,
];
export default () => {
useEffect(() => {
@ -93,6 +70,7 @@ export default () => {
const width = image.getWidth();
const height = image.getHeight();
const values = await image.readRasters();
console.log(values)
return { rasterData: values[0], width, height };
},
},
@ -100,13 +78,13 @@ export default () => {
);
layer.source(tileSource).style({
domain: [0.001, 11.001],
// domain: [0, 255],
clampLow: false,
rampColors: {
type:"cat",
colors: colorList,
positions,
// colors: ['#f00', '#f00'],
// positions: [0, 1]
},
});

View File

@ -1,8 +1,8 @@
import { IColorRamp } from '@antv/l7-utils';
import { ITexture2D } from '../renderer/ITexture2D';
export interface ITextureService {
setColorTexture(texture: ITexture2D,colorRamp: IColorRamp):void;
getColorTexture(colorRamp: IColorRamp): ITexture2D
setColorTexture(texture: ITexture2D,colorRamp: IColorRamp,domain?:[number,number]):void;
getColorTexture(colorRamp: IColorRamp, domain?:[number,number]): ITexture2D
destroy():void;
}

View File

@ -406,7 +406,7 @@ export interface ILayer {
field: StyleAttributeField,
values?: StyleAttributeOption,
updateOptions?: Partial<IStyleAttributeUpdateOptions>,
): void;
): boolean;
setLayerPickService(layerPickService:ILayerPickService):void;
init(): Promise<void>;
scale(field: string | number | IScaleOptions, cfg?: IScale): ILayer;

View File

@ -94,6 +94,7 @@ export interface ITexture2DInitializationOptions {
export interface ITexture2D {
get(): unknown;
getSize():[number,number];
update(options: any): void;
bind(): void;
resize(options: { width: number; height: number }): void;

View File

@ -57,7 +57,7 @@ import Source from '@antv/l7-source';
import { encodePickingColor, WorkerSourceMap } from '@antv/l7-utils';
import { EventEmitter } from 'eventemitter3';
import { Container } from 'inversify';
import { isFunction, isObject, isUndefined } from 'lodash';
import { isEqual, isFunction, isObject, isUndefined } from 'lodash';
import { BlendTypes } from '../utils/blend';
import { styleDataMapping } from '../utils/dataMappingStyle';
import { calculateData } from '../utils/layerData';
@ -140,7 +140,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}>
// 每个 Layer 都有一个
public multiPassRenderer: IMultiPassRenderer;
// 注入插件
// 注入插件
public plugins: ILayerPlugin[];
public startInit: boolean = false;
@ -538,8 +538,13 @@ export default class BaseLayer<ChildLayerStyleOptions = {}>
values?: StyleAttributeOption,
updateOptions?: Partial<IStyleAttributeUpdateOptions>,
) {
this.updateStyleAttribute('filter', field, values, updateOptions);
this.dataState.dataSourceNeedUpdate = true;
const flag = this.updateStyleAttribute(
'filter',
field,
values,
updateOptions,
);
this.dataState.dataSourceNeedUpdate = flag;
return this;
}
@ -552,8 +557,13 @@ export default class BaseLayer<ChildLayerStyleOptions = {}>
field,
values,
};
this.updateStyleAttribute('shape', field, values, updateOptions);
this.dataState.dataSourceNeedUpdate = true; // 通过数据更新驱动shape 更新
const flag = this.updateStyleAttribute(
'shape',
field,
values,
updateOptions,
);
this.dataState.dataSourceNeedUpdate = flag;
return this;
}
public label(
@ -1338,7 +1348,14 @@ export default class BaseLayer<ChildLayerStyleOptions = {}>
field: StyleAttributeField,
values?: StyleAttributeOption,
updateOptions?: Partial<IStyleAttributeUpdateOptions>,
) {
): boolean {
// encode diff
const preAttribute = this.configService.getAttributeConfig(this.id) || {};
// @ts-ignore
if (isEqual(preAttribute[type], { field, values })) {
return false;
}
// 存储 Attribute
if (
[
@ -1358,6 +1375,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}>
},
});
}
if (!this.startInit) {
// 开始初始化执行
this.pendingStyleAttributes.push({
@ -1385,6 +1403,7 @@ export default class BaseLayer<ChildLayerStyleOptions = {}>
updateOptions,
);
}
return true;
}
public getLayerAttributeConfig(): Partial<ILayerAttributesOption> {

View File

@ -6,7 +6,14 @@ import {
TYPES,
} from '@antv/l7-core';
import { generateColorRamp, IColorRamp } from '@antv/l7-utils';
import {
generateCatRamp,
generateColorRamp,
generateCustomRamp,
generateLinearRamp,
generateQuantizeRamp,
IColorRamp,
} from '@antv/l7-utils';
export default class TextureService implements ITextureService {
private layer: ILayer;
@ -20,21 +27,21 @@ export default class TextureService implements ITextureService {
TYPES.IRendererService,
);
}
public getColorTexture(colorRamp: IColorRamp) {
public getColorTexture(colorRamp: IColorRamp, domain?: [number, number]) {
// TODO 支持传入图片
const currentkey = this.getTextureKey(colorRamp);
const currentkey = this.getTextureKey(colorRamp, domain);
if (this.key === currentkey) {
return this.colorTexture;
} else {
this.createColorTexture(colorRamp);
this.createColorTexture(colorRamp, domain);
}
this.key = currentkey;
return this.colorTexture;
}
public createColorTexture(colorRamp: IColorRamp) {
public createColorTexture(colorRamp: IColorRamp, domain?: [number, number]) {
const { createTexture2D } = this.rendererService;
const imageData = generateColorRamp(colorRamp) as ImageData;
const imageData = this.getColorRampBar(colorRamp, domain) as ImageData;
const texture = createTexture2D({
data: imageData.data,
width: imageData.width,
@ -45,8 +52,12 @@ export default class TextureService implements ITextureService {
return texture;
}
public setColorTexture(texture: ITexture2D, colorRamp: IColorRamp) {
this.key = this.getTextureKey(colorRamp);
public setColorTexture(
texture: ITexture2D,
colorRamp: IColorRamp,
domain: [number, number],
) {
this.key = this.getTextureKey(colorRamp, domain);
this.colorTexture = texture;
}
@ -54,7 +65,27 @@ export default class TextureService implements ITextureService {
this.colorTexture?.destroy();
}
private getTextureKey(colorRamp: IColorRamp): string {
return `${colorRamp.colors.join('_')}_${colorRamp.positions.join('_')}`;
private getColorRampBar(colorRamp: IColorRamp, domain?: [number, number]) {
switch (colorRamp.type) {
case 'cat':
return generateCatRamp(colorRamp);
case 'quantize':
return generateQuantizeRamp(colorRamp);
case 'custom':
return generateCustomRamp(colorRamp, domain as [number, number]);
case 'linear':
return generateLinearRamp(colorRamp, domain as [number, number]);
default:
return generateColorRamp(colorRamp) as ImageData;
}
}
private getTextureKey(
colorRamp: IColorRamp,
domain?: [number, number],
): string {
return `${colorRamp.colors.join('_')}_${colorRamp?.positions?.join('_')}_${
colorRamp.type
}_${domain?.join('_')}`;
}
}

View File

@ -43,9 +43,7 @@ export default class DataMappingPlugin implements ILayerPlugin {
return flag;
}
layer.dataState.dataMappingNeedUpdate = false;
this.generateMaping(layer, { styleAttributeService });
return true;
return this.generateMaping(layer, { styleAttributeService });
},
);
@ -72,7 +70,6 @@ export default class DataMappingPlugin implements ILayerPlugin {
return this.applyAttributeMapping(filter, record)[0];
});
}
if (attributesToRemapping.length) {
// 过滤数据
const encodeData = this.mapping(
@ -83,6 +80,7 @@ export default class DataMappingPlugin implements ILayerPlugin {
);
layer.setEncodedData(encodeData);
}
// 处理文本更新,更新文字形状
// layer.emit('remapping', null);
});
@ -96,7 +94,6 @@ export default class DataMappingPlugin implements ILayerPlugin {
const attributes = styleAttributeService.getLayerStyleAttributes() || [];
const filter = styleAttributeService.getLayerStyleAttribute('filter');
const { dataArray } = layer.getSource().data;
let filterData = dataArray;
// 数据过滤完 再执行数据映射
if (filter?.scale) {
@ -111,8 +108,13 @@ export default class DataMappingPlugin implements ILayerPlugin {
filterData = layer.processData(filterData);
const encodeData = this.mapping(layer, attributes, filterData, undefined);
layer.setEncodedData(encodeData);
if (dataArray.length === 0 && layer.encodeDataLength === 0) {
return false;
}
// 对外暴露事件
layer.emit('dataUpdate', null);
return true;
}
private mapping(

View File

@ -5,7 +5,7 @@ import {
IModel,
ITexture2D,
} from '@antv/l7-core';
import { generateColorRamp, getMask, IColorRamp } from '@antv/l7-utils';
import { getMask,getDefaultDomain } from '@antv/l7-utils';
import BaseModel from '../../core/BaseModel';
import { IRasterLayerStyleOptions } from '../../core/interface';
import { RasterImageTriangulation } from '../../core/triangulation';
@ -14,22 +14,21 @@ import rasterVert from '../shaders/raster_2d_vert.glsl';
export default class RasterModel extends BaseModel {
protected texture: ITexture2D;
protected colorTexture: ITexture2D;
private rampColors: any;
public getUninforms() {
const {
opacity = 1,
clampLow = true,
clampHigh = true,
noDataValue = -9999999,
domain = [0, 1],
domain,
rampColors,
} = this.layer.getLayerConfig() as IRasterLayerStyleOptions;
this.colorTexture = this.layer.textureService.getColorTexture(rampColors);
const newdomain = domain ||getDefaultDomain(rampColors)
this.colorTexture = this.layer.textureService.getColorTexture(rampColors,newdomain);
return {
u_opacity: opacity || 1,
u_texture: this.texture,
u_domain: domain,
u_domain: newdomain,
u_clampLow: clampLow,
u_clampHigh: typeof clampHigh !== 'undefined' ? clampHigh : clampLow,
u_noDataValue: noDataValue,
@ -123,17 +122,4 @@ export default class RasterModel extends BaseModel {
});
}
private updateColorTexture() {
const { createTexture2D } = this.rendererService;
const {
rampColors,
} = this.layer.getLayerConfig() as IRasterLayerStyleOptions;
const imageData = generateColorRamp(rampColors as IColorRamp);
this.colorTexture = createTexture2D({
data: imageData.data,
width: imageData.width,
height: imageData.height,
flipY: false,
});
}
}

View File

@ -6,7 +6,7 @@ import {
IModelUniform,
ITexture2D,
} from '@antv/l7-core';
import { getMask } from '@antv/l7-utils';
import { getMask,getDefaultDomain } from '@antv/l7-utils';
import BaseModel from '../../core/BaseModel';
import { IRasterLayerStyleOptions } from '../../core/interface';
import { RasterImageTriangulation } from '../../core/triangulation';
@ -21,20 +21,21 @@ import {
clampLow = true,
clampHigh = true,
noDataValue = -9999999,
domain = [0, 1],
domain,
rampColors,
colorTexture
} = this.layer.getLayerConfig() as IRasterLayerStyleOptions;
const newdomain = domain ||getDefaultDomain(rampColors)
let texture:ITexture2D | undefined = colorTexture;
if(!colorTexture) {
texture = this.layer.textureService.getColorTexture(rampColors) as ITexture2D;
texture = this.layer.textureService.getColorTexture(rampColors,newdomain) as ITexture2D;
} else {
this.layer.textureService.setColorTexture(colorTexture,rampColors)
this.layer.textureService.setColorTexture(colorTexture,rampColors,newdomain)
}
return {
u_opacity: opacity || 1,
u_texture: this.texture,
u_domain: domain,
u_domain: newdomain,
u_clampLow: clampLow,
u_clampHigh: typeof clampHigh !== 'undefined' ? clampHigh : clampLow,
u_noDataValue: noDataValue,

View File

@ -1,101 +0,0 @@
import {
AttributeType,
gl,
IEncodeFeature,
IModel,
ITexture2D,
} from '@antv/l7-core';
import { getMask } from '@antv/l7-utils';
import BaseModel from '../../core/BaseModel';
import { IRasterLayerStyleOptions } from '../../core/interface';
import { RasterImageTriangulation } from '../../core/triangulation';
import rasterFrag from '../shaders/raster_2d_frag.glsl';
import rasterVert from '../shaders/raster_2d_vert.glsl';
export default class RasterModel extends BaseModel {
protected texture: ITexture2D;
public getUninforms() {
const { createTexture2D } = this.rendererService;
const {
colorTexture = createTexture2D({
data: [],
width: 0,
height: 0,
flipY: false,
}),
opacity = 1,
clampLow = true,
clampHigh = true,
noDataValue = -9999999,
domain = [0, 1],
} = this.layer.getLayerConfig() as IRasterLayerStyleOptions;
return {
u_opacity: opacity || 1,
u_texture: this.texture,
u_domain: domain,
u_clampLow: clampLow,
u_clampHigh: typeof clampHigh !== 'undefined' ? clampHigh : clampLow,
u_noDataValue: noDataValue,
u_colorTexture: colorTexture,
};
}
public async initModels(): Promise<IModel[]> {
const {
mask = false,
maskInside = true,
} = this.layer.getLayerConfig() as IRasterLayerStyleOptions;
const source = this.layer.getSource();
const { createTexture2D } = this.rendererService;
const parserDataItem = source.data.dataArray[0];
this.texture = createTexture2D({
data: parserDataItem.data,
width: parserDataItem.width,
height: parserDataItem.height,
format: gl.LUMINANCE,
type: gl.FLOAT,
});
const model = await this.layer
.buildLayerModel({
moduleName: 'rasterTileImageData',
vertexShader: rasterVert,
fragmentShader: rasterFrag,
triangulation: RasterImageTriangulation,
depth: { enable: false },
stencil: getMask(mask, maskInside),
})
return [model]
}
public async buildModels():Promise<IModel[]> {
return await this.initModels();
}
public clearModels(): void {
this.texture?.destroy();
}
protected registerBuiltinAttributes() {
this.styleAttributeService.registerStyleAttribute({
name: 'uv',
type: AttributeType.Attribute,
descriptor: {
name: 'a_Uv',
buffer: {
usage: gl.DYNAMIC_DRAW,
data: [],
type: gl.FLOAT,
},
size: 2,
update: (
feature: IEncodeFeature,
featureIdx: number,
vertex: number[],
) => {
return [vertex[3], vertex[4]];
},
},
});
}
}

View File

@ -20,6 +20,7 @@ void main() {
else if ((!u_clampLow && value < u_domain[0]) || (!u_clampHigh && value > u_domain[1]))
gl_FragColor = vec4(0, 0, 0, 0);
else {
float normalisedValue =(value - u_domain[0]) / (u_domain[1] -u_domain[0]);
vec4 color = texture2D(u_colorTexture,vec2(normalisedValue, 0));
gl_FragColor = color;

View File

@ -1,5 +1,4 @@
import { ILayerAttributesOption } from '@antv/l7-core';
// import RasterLayer from './layers/RasterDataLayer';
import RasterLayer from '../../raster'
import Tile from './Tile';

View File

@ -2,6 +2,7 @@ import { ILayerAttributesOption, ITexture2D } from '@antv/l7-core';
import RasterLayer from '../../raster'
import { IRasterLayerStyleOptions } from '../../core/interface';
import Tile from './Tile';
import { getDefaultDomain } from '@antv/l7-utils';
const DEFAULT_COLOR_TEXTURE_OPTION = {
positions: [0, 1],
@ -12,9 +13,10 @@ export default class RasterTile extends Tile {
private colorTexture: ITexture2D;
public async initTileLayer(): Promise<void> {
const attributes = this.parent.getLayerAttributeConfig();
const layerOptions = this.getLayerOptions();
const layerOptions = this.getLayerOptions() ;
const sourceOptions = this.getSourceOption();
this.colorTexture = this.parent.textureService.getColorTexture((layerOptions as unknown as IRasterLayerStyleOptions).rampColors)
const {rampColors,domain} = this.getLayerOptions() as unknown as IRasterLayerStyleOptions;
this.colorTexture = this.parent.textureService.getColorTexture(rampColors,domain);
const layer = new RasterLayer({
...layerOptions,
colorTexture: this.colorTexture,
@ -57,8 +59,8 @@ export default class RasterTile extends Tile {
*/
public styleUpdate(...arg: any): void {
const { rampColors = DEFAULT_COLOR_TEXTURE_OPTION } = arg;
this.colorTexture = this.parent.textureService.getColorTexture(rampColors)
const { rampColors = DEFAULT_COLOR_TEXTURE_OPTION, domain} = arg as IRasterLayerStyleOptions;
this.colorTexture = this.parent.textureService.getColorTexture(rampColors,domain || getDefaultDomain(rampColors))
this.layers.forEach(layer => layer.style({ colorTexture: this.colorTexture }));
}

View File

@ -1,28 +0,0 @@
import BaseLayer from '../../../core/BaseLayer';
import { IRasterLayerStyleOptions } from '../../../core/interface';
import RasterModel from '../../../raster/models/rasterTile';
import RasterRgbModel from '../../../raster/models/rasterRgb';
export default class RasterTiffLayer extends BaseLayer<
Partial<IRasterLayerStyleOptions>
> {
public type: string = 'RasterLayer';
public async buildModels() {
const model = this.getModel();
this.layerModel = new model(this);
await this.initLayerModels();
}
public getModel() {
const type = this.getModelType();
return type === 'rasterRgb' ? RasterRgbModel :RasterModel;
}
public getModelType():string {
return this.layerSource.parser.type === 'rasterRgb' ? 'rasterRgb' : 'raster'
}
protected getDefaultConfig() {
return {};
}
}

View File

@ -98,6 +98,10 @@ export default class ReglTexture2D implements ITexture2D {
this.height = height;
}
public getSize(): [number, number] {
return [this.width, this.height];
}
public destroy() {
if (!this.isDestroy) {
this.texture?.destroy();

View File

@ -0,0 +1,72 @@
### rampColors 颜色色带
- type 类型 支持 `linear','quantize','custom','cat'
- colors  颜色数组
- positions 数据分段区间可选quantize 不需要设置 positionposition 为原始数据值
⚠️ 2.13 新增特性
#### cat 枚举类型色带
枚举类型色带只支持 0 -255 的整数类型positions 用来设置枚举
```tsx
{
type:'cat',
colors:['#e41a1c','#377eb8','#4daf4a','#984ea3','#ff7f00'],
positions:[1,20,101,102,200],
}
```
#### quantize 等间距分类色带
等间距只根据数据的区间 domain 进行均匀分段,如 domain [0,10000],如果分 5 段,每段间距 2000。
等间距不需要设置 positions只需要设置colors根据colors 的长度设置分段数
```tsx
rampColors: {
type:'quantize',
colors: ['#f0f9e8','#bae4bc','#7bccc4','#43a2ca','#0868ac']
}
```
#### linear 线性连续色带
linear 为现有连续类型的加强版positions 支持设置源数据,不需要转换成 0-1
```tsx
rampColors: {
type:'linear',
colors: ['#f0f9e8','#bae4bc','#7bccc4','#43a2ca','#0868ac'],
positions [0,200,1000,4000,8000]
}
⚠️ 兼容 2.13.0 之前版本未设置type 时position 值域为 0-1。
```
#### custom 自定义分段色带
自定义分段色带区别等间距色带,用户自定义分段间隔。
自定义 positions 的长度需要比 colors 的长度多1个同时poisitions
```tsx
rampColors: {
type:'custom',
colors: ['#f0f9e8','#bae4bc','#7bccc4','#43a2ca','#0868ac'],
positions [0,200,1000,4000,8000,10000]
}
```
配置值域映射颜色的色带,值域的范围为 `[0 - 1]`, 对应的我们需要为每一个 `position` 位置设置一个颜色值。
⚠️ colors, positions 的长度要相同
```javascript
layer.style({
rampColors: {
colors: ['#FF4818', '#F7B74A', '#FFF598', '#91EABC', '#2EA9A1', '#206C7C'],
positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0],
},
});

View File

@ -19,20 +19,5 @@ layer.style({
| noDataValue | `number` | 不会显示的值 | `-9999999` |
| rampColors | `IRampColors` | 值域映射颜色的色带 | `/` |
#### rampColors
- colors  颜色数组
- positions 数据区间
配置值域映射颜色的色带,值域的范围为 `[0 - 1]`, 对应的我们需要为每一个 `position` 位置设置一个颜色值。
⚠️ colors, positions 的长度要相同
```javascript
layer.style({
rampColors: {
colors: ['#FF4818', '#F7B74A', '#FFF598', '#91EABC', '#2EA9A1', '#206C7C'],
positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0],
},
});
```
<embed src="@/docs/api/raster_layer/common/rampcolors.md"></embed>

View File

@ -17,20 +17,4 @@ layer.style({
| noDataValue | `number` | 不会显示的值 | `-9999999` |
| rampColors | `IRampColors` | 值域映射颜色的色带 | `/` |
#### rampColors
- colors  颜色数组
- positions 数据区间
配置值域映射颜色的色带,值域的范围为 `[0 - 1]`, 对应的我们需要为每一个 `position` 位置设置一个颜色值。
⚠️ colors, positions 的长度要相同
```javascript
layer.style({
rampColors: {
colors: ['#FF4818', '#F7B74A', '#FFF598', '#91EABC', '#2EA9A1', '#206C7C'],
positions: [0, 0.2, 0.4, 0.6, 0.8, 1.0],
},
});
```
<embed src="@/docs/api/raster_layer/common/rampcolors.md"></embed>

View File

@ -12,7 +12,7 @@ order: 0
- L7 本身内部没有提供栅格数据格式, 需要将外部的栅格数据文件解析后或者提供解析方法做传入、如 `tiff`、`lerc`。
- 栅格图层除了支持简单渲染之外还支持栅格数据的多波段计算,可以用于绘制遥感彩色影像。
### 直接绘制
### 数据绘制
我们可以直接在外部计算出栅格的波段数据后传给栅格图层使用。

View File

@ -67,13 +67,13 @@ layer.on('legend:color', (ev) => console.log(ev));
### legend:size
数据映射更新,图例发生变化,color 大小改变
数据映射更新,图例发生变化,size 大小改变
参数 option
- type 映射通道、图例类型
- attr 映射实例
```js
layer.on('legend:color', (ev) => console.log(ev));
layer.on('legend:size', (ev) => console.log(ev));
```

View File

@ -10,11 +10,6 @@
"title": "雷达图",
"screenshot": "https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*JDO-R5XU7xwAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "light.js",
"title": "夜光图",
"screenshot": "https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*xznhSJFEAXYAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "image.js",
"title": "图片",

View File

@ -1,4 +1,4 @@
---
title: 栅格图层
title: 图片栅格
order: 0
---

View File

@ -1,4 +1,4 @@
---
title: 数据栅格
title: 多波段
order: 0
---

View File

@ -0,0 +1,50 @@
// https://gw.alipayobjects.com/zos/antvdemo/assets/2019_clip/ndvi_201905.tif
import { RasterLayer, Scene } from '@antv/l7';
import { GaodeMap } from '@antv/l7-maps';
import * as GeoTIFF from 'geotiff';
const scene = new Scene({
id: 'map',
map: new GaodeMap({
center: [121.268, 30.3628],
zoom: 3,
}),
});
async function getTiffData() {
const response = await fetch(
'https://gw.alipayobjects.com/os/rmsportal/XKgkjjGaAzRyKupCBiYW.dat',
);
const arrayBuffer = await response.arrayBuffer();
return arrayBuffer;
}
scene.on('loaded', async () => {
const tiffdata = await getTiffData();
const tiff = await GeoTIFF.fromArrayBuffer(tiffdata);
const image = await tiff.getImage();
const width = image.getWidth();
const height = image.getHeight();
const values = await image.readRasters();
const layer = new RasterLayer();
layer
.source(values[0], {
parser: {
type: 'raster',
width,
height,
extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963],
},
})
.style({
opacity: 1,
clampLow: false,
clampHigh: false,
domain: [0, 10000],
rampColors: {
type:'custom',
colors: ['#b2182b','#d6604d','#f4a582','#fddbc7','#f7f7f7','#d1e5f0','#92c5de','#4393c3','#2166ac'],
positions: [0, 50, 200, 500, 2000, 3000, 4000, 5000, 8000,10000],
},
});
scene.addLayer(layer)
});

View File

@ -0,0 +1,50 @@
// https://gw.alipayobjects.com/zos/antvdemo/assets/2019_clip/ndvi_201905.tif
import { RasterLayer, Scene } from '@antv/l7';
import { GaodeMap } from '@antv/l7-maps';
import * as GeoTIFF from 'geotiff';
const scene = new Scene({
id: 'map',
map: new GaodeMap({
center: [121.268, 30.3628],
zoom: 3,
}),
});
async function getTiffData() {
const response = await fetch(
'https://gw.alipayobjects.com/os/rmsportal/XKgkjjGaAzRyKupCBiYW.dat',
);
const arrayBuffer = await response.arrayBuffer();
return arrayBuffer;
}
scene.on('loaded', async () => {
const tiffdata = await getTiffData();
const tiff = await GeoTIFF.fromArrayBuffer(tiffdata);
const image = await tiff.getImage();
const width = image.getWidth();
const height = image.getHeight();
const values = await image.readRasters();
const layer = new RasterLayer();
layer
.source(values[0], {
parser: {
type: 'raster',
width,
height,
extent: [73.482190241, 3.82501784112, 135.106618732, 57.6300459963],
},
})
.style({
opacity: 1,
clampLow: false,
clampHigh: false,
domain: [0, 10000],
rampColors: {
type:'quantize', // 等间距 不需要设置 position
colors: ['#b2182b','#d6604d','#f4a582','#fddbc7','#f7f7f7','#d1e5f0','#92c5de','#4393c3','#2166ac'],
},
});
scene.addLayer(layer)
});

View File

@ -49,8 +49,9 @@ async function addLayer() {
domain: [ 0, 90 ],
nodataValue: 0,
rampColors: {
colors: [ 'rgba(92,58,16,0)', 'rgba(92,58,16,0)', '#fabd08', '#f1e93f', '#f1ff8f', '#fcfff7' ],
positions: [ 0, 0.05, 0.1, 0.25, 0.5, 1.0 ]
type:'linear', // 2.13.0 及以后版本支持
colors: ['rgba(92,58,16,0)','rgba(92,58,16,0)', '#fabd08', '#f1e93f', '#f1ff8f', '#fcfff7' ],
positions: [0,3, 9, 22.5, 45, 90 ]
}
});

View File

@ -0,0 +1,59 @@
// https://gw.alipayobjects.com/zos/antvdemo/assets/2019_clip/ndvi_201905.tif
import { RasterLayer, Scene } from '@antv/l7';
import { GaodeMap } from '@antv/l7-maps';
import * as GeoTIFF from 'geotiff';
const scene = new Scene({
id: 'map',
map: new GaodeMap({
style: 'dark',
center: [ 105, 37.5 ],
zoom: 2.5
})
});
scene.on('loaded', () => {
addLayer();
});
async function getTiffData() {
const response = await fetch(
'https://gw.alipayobjects.com/zos/antvdemo/assets/light_clip/lightF182013.tiff'
);
const arrayBuffer = await response.arrayBuffer();
const tiff = await GeoTIFF.fromArrayBuffer(arrayBuffer);
const image = await tiff.getImage();
const width = image.getWidth();
const height = image.getHeight();
const values = await image.readRasters();
return {
data: values[0],
width,
height
};
}
async function addLayer() {
const tiffdata = await getTiffData();
const layer = new RasterLayer({});
layer
.source(tiffdata.data, {
parser: {
type: 'raster',
width: tiffdata.width,
height: tiffdata.height,
extent: [ 73.4821902409999979, 3.8150178409999995, 135.1066187319999869, 57.6300459959999998 ]
}
})
.style({
clampLow: false,
clampHigh: false,
domain: [ 0, 90 ],
nodataValue: 0,
rampColors: {
positions: [ 0, 0.05, 0.1, 0.25, 0.5, 1.0 ],// 数据需要换成 0-1
colors: ['rgba(92,58,16,0)','rgba(92,58,16,0)', '#fabd08', '#f1e93f', '#f1ff8f', '#fcfff7' ],
}
});
scene.addLayer(layer);
}

View File

@ -0,0 +1,29 @@
{
"title": {
"zh": "单波段数据栅格",
"en": "Gallery"
},
"demos": [
{
"filename": "light_default.js",
"title": "夜光图-默认线性",
"screenshot": "https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*xznhSJFEAXYAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "light.js",
"title": "夜光图-线性色带",
"screenshot": "https://gw.alipayobjects.com/mdn/rms_816329/afts/img/A*xznhSJFEAXYAAAAAAAAAAAAAARQnAQ"
},
{
"filename": "dem.js",
"title": "高程图-自定义色带",
"screenshot": "https://mdn.alipayobjects.com/huamei_qa8qxu/afts/img/A*_RtOTrYOOrUAAAAAAAAAAAAADmJ7AQ/original"
},
{
"filename": "dem_quantize.js",
"title": "高程图-等间距色带",
"screenshot": "https://mdn.alipayobjects.com//huamei_qa8qxu/afts/img/A*DT1HQbl4JKMAAAAAAAAAAAAADmJ7AQ/original"
}
]
}

View File

@ -0,0 +1,4 @@
---
title: Raster Data Map
order: 0
---

View File

@ -0,0 +1,4 @@
---
title: 单波段
order: 0
---

View File

@ -4,56 +4,33 @@ import * as GeoTIFF from 'geotiff';
const colorList = [
'#419bdf', // Water
'#419bdf',
'#397d49', // Tree
'#397d49',
'#358221', // Tree
'#88b053', // Grass
'#88b053',
'#7a87c6', // vegetation
'#7a87c6',
'#e49635', // Crops
'#e49635',
'#dfc35a', // shrub
'#dfc35a',
'#c4281b', // Built Area
'#c4281b',
'#a59b8f', // Bare ground
'#a59b8f',
'#ED022A', // Built Area
'#a8ebff', // Snow
'#a8ebff',
'#616161', // Clouds
'#616161'
'#EDE9E4', // Bare ground
'#F2FAFF', // Snow
'#C8C8C8', // Clouds
];
const positions = [
0.0,
0.1,
0.1,
0.2,
0.2,
0.3,
0.3,
0.4,
0.4,
0.5,
0.5,
0.6,
0.6,
0.7,
0.7,
0.8,
0.8,
0.9,
0.9,
1.0
1,2,3,4,5,6,7,8,9,10,11,
];
const scene = new Scene({
@ -97,9 +74,10 @@ scene.on('loaded', () => {
layer.source(tileSource)
.style({
domain: [ 0.001, 11.001 ],
domain: [ 0, 255],// 枚举类型domain 必须为0-255
clampLow: false,
rampColors: {
type:'cat',
colors: colorList,
positions
}
@ -115,7 +93,7 @@ const wrap = document.getElementById('map');
const legend = document.createElement('div');
const data = [];
for (let i = 0; i < colorList.length; i += 2) {
for (let i = 0; i < colorList.length; i += 1) {
data.push({
color: colorList[i],
text: [
@ -129,7 +107,7 @@ for (let i = 0; i < colorList.length; i += 2) {
'Bare ground',
'Snow',
'Clouds'
][i / 2]
][i]
});
}
const strArr = [];

View File

@ -1,9 +1,10 @@
import * as d3 from 'd3-color';
import { Context } from 'vm';
import { $window, isMini } from './mini-adapter';
export interface IColorRamp {
type?: 'cat' | 'linear' | 'quantize' | 'custom'
positions: number[];
colors: string[];
weights?: number[];
}
export function isColor(str: any) {
@ -51,6 +52,7 @@ export interface IImagedata {
height: number;
}
// 连续型 老版本兼容
export function generateColorRamp(
colorRamp: IColorRamp,
): ImageData | IImagedata {
@ -60,17 +62,6 @@ export function generateColorRamp(
canvas.height = 1;
let data = null;
if (colorRamp.weights) {
// draw enum color
let count = 0;
colorRamp.weights.map((w, index) => {
const color = colorRamp.colors[index] || 'rgba(0, 0, 0, 0)';
const stop = count + w;
ctx.fillStyle = color;
ctx.fillRect(count * 256, 0, stop * 256, 1);
count = stop;
});
} else {
// draw linear color
const gradient = ctx.createLinearGradient(0, 0, 256, 1);
@ -82,7 +73,7 @@ export function generateColorRamp(
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 256, 1);
}
if (!isMini) {
data = ctx.getImageData(0, 0, 256, 1).data;
@ -108,3 +99,149 @@ export function generateColorRamp(
return { data, width: 256, height: 1 };
}
}
// 连续型 Position 支持设置原始数据
export function generateLinearRamp(
colorRamp: IColorRamp,
domain: [number, number],
): ImageData | IImagedata {
let canvas = $window.document.createElement('canvas');
let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
canvas.width = 256;
canvas.height = 1;
// draw linear color
const gradient = ctx.createLinearGradient(0, 0, 256, 1);
const step = domain[1] - domain[0];
for (let i = 0; i < colorRamp.colors.length; ++i) {
const value = Math.max((colorRamp.positions[i] - domain[0]) / step,0);
console.log(value)
gradient.addColorStop(value, colorRamp.colors[i]);
}
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 256, 1);
const data = ctx.getImageData(0, 0, 256, 1).data;
const imageData = toIEIMageData(ctx, data);
// @ts-ignore
canvas = null;
// @ts-ignore
ctx = null;
return imageData
}
// 枚举类型
export function generateCatRamp(
colorRamp: IColorRamp,
): ImageData | IImagedata {
let canvas = $window.document.createElement('canvas');
let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
canvas.width = 256;
canvas.height = 1;
const imageData = ctx.createImageData(256, 1);
imageData.data.fill(0);
colorRamp.positions.forEach((p: number, index: number) => {
const colorArray = rgb2arr(colorRamp.colors[index])
imageData.data[p * 4 + 0] = colorArray[0] * 255;
imageData.data[p * 4 + 1] = colorArray[1] * 255;
imageData.data[p * 4 + 2] = colorArray[2] * 255;
imageData.data[p * 4 + 3] = colorArray[3] * 255;
})
// @ts-ignore
canvas = null;
// @ts-ignore
ctx = null;
return imageData;
}
// 等间距
export function generateQuantizeRamp(
colorRamp: IColorRamp,
): ImageData | IImagedata {
let canvas = $window.document.createElement('canvas');
let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
ctx.globalAlpha = 1.0
canvas.width = 256;
canvas.height = 1;
const step = 256 / colorRamp.colors.length;// TODO 精度问题
// draw linear color
for (let i = 0; i < colorRamp.colors.length; i++) {
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = colorRamp.colors[i];
ctx.moveTo(i * step, 0); // positioned at 50,25
ctx.lineTo((i + 1) * step, 0);
ctx.stroke();
}
const data = ctx.getImageData(0, 0, 256, 1).data;
// 使用 createImageData 替代 new ImageData、兼容 IE11
const imageData = toIEIMageData(ctx, data);
// @ts-ignore
canvas = null;
// @ts-ignore
ctx = null;
return imageData;
}
// 自定义间距
export function generateCustomRamp(
colorRamp: IColorRamp,
domain: [number, number],
): ImageData | IImagedata {
let canvas = $window.document.createElement('canvas');
let ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
ctx.globalAlpha = 1.0
canvas.width = 256;
canvas.height = 1;
const step = domain[1] - domain[0];
if(colorRamp.positions.length - colorRamp.colors.length !==1) {
console.warn('positions 的数字个数应当比 colors 的样式多一个,poisitions 的首尾值一般为数据的最大最新值')
}
for (let i = 0; i < colorRamp.colors.length; i++) {
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = colorRamp.colors[i];
ctx.moveTo((colorRamp.positions[i] - domain[0]) / step * 255, 0); // positioned at 50,25
ctx.lineTo((colorRamp.positions[i + 1]- domain[0]) / step * 255, 0);
ctx.stroke();
}
const data = ctx.getImageData(0, 0, 256, 1).data;
const imageData = toIEIMageData(ctx, data);
// @ts-ignore
canvas = null;
// @ts-ignore
ctx = null;
return imageData;
}
function toIEIMageData(ctx: Context, data: Uint8ClampedArray) {
const imageData = ctx.createImageData(256, 1);
for (let i = 0; i < imageData.data.length; i += 4) {
imageData.data[i + 0] = data[i + 0];
imageData.data[i + 1] = data[i + 1];
imageData.data[i + 2] = data[i + 2];
imageData.data[i + 3] = data[i + 3];
}
return imageData
}
export function getDefaultDomain(rampColors:IColorRamp) {
switch (rampColors.type) {
case 'cat' :
return [0,255]
default:
[0,1]
}
}

View File

@ -2,7 +2,7 @@ import earcut from 'earcut';
import { calculateCentroid } from '../geo';
import ExtrudePolyline from './extrude_polyline';
import { IEncodeFeature } from './interface';
const LATMAX = 85.0511287798;
export function LineTriangulation(feature: IEncodeFeature) {
const { coordinates, originCoordinates, version } = feature;
// let path = coordinates as number[][][] | number[][];
@ -91,9 +91,17 @@ export function polygonFillTriangulation(feature: IEncodeFeature) {
}
function project_mercator(x: number, y: number) {
let y1 = y;
if (y > LATMAX) {
y1 = LATMAX;
}
if (y < -LATMAX) {
y1 = -LATMAX;
}
return [
(Math.PI * x) / 180 + Math.PI,
Math.PI - Math.log(Math.tan(Math.PI * 0.25 + ((Math.PI * y) / 180) * 0.5)),
Math.PI - Math.log(Math.tan(Math.PI * 0.25 + ((Math.PI * y1) / 180) * 0.5)),
];
}