Работа с геометриями
Для манипуляций с GeoJSON рекомендуется использовать библиотеку turf.js. Ниже описывается использование данной библиотеки в связке с MapGL JS API.
Граничный прямоугольник (bbox)
Метод turf.bbox
рассчитывает прямоугольную область, в которой находятся заданные объекты или группа объектов.
const line = turf.lineString([
[34, 40],
[36, 41],
[41, 37],
[48, 42],
[42, 35],
]);
const bbox = turf.bbox(line); // [minX, minY, maxX, maxY]
Документация на метод: @turf/bbox
Примеры использования
-
Спозиционировать карту так, чтобы все объекты поместились на экран при помощи метода map.fitBounds.
map.fitBounds({ southWest: bbox.slice(0, 2), northEast: bbox.slice(2, 4) });
-
Запросить дополнительные данные к имеющимся. Например, имея полигоны муниципальных районов, запросить близлежащие объекты застройки.
Граничный прямоугольник является наиболее распространенным и быстрым способом определить границы нахождения объектов. Обратной стороной является то, что границы таким образом определяются достаточно грубо. Если нужна большая точность, воспользуйтесь выпуклой оболочкой.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using @turf/bbox with MapGL</title>
<meta name="description" content="Using @turf/bbox with MapGL" />
<style>
html,
body,
#container {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
const line = turf.lineString([[34, 40], [36, 41], [41, 37], [48, 42], [42, 35]]);
const bbox = turf.bbox(line);
const geojson = turf.featureCollection([line, turf.bboxPolygon(bbox)]);
const map = new mapgl.Map('container', {
center: turf.getCoord(turf.center(geojson)),
zoom: 5,
key: 'Your API access key',
});
new mapgl.GeoJsonSource(map, {
data: geojson,
attributes: {
foo: 'bar'
}
});
map.on('styleload', () => {
map.addLayer({
id: 'polygons',
type: 'polygon',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DD50'
}
});
map.addLayer({
id: 'lines',
type: 'line',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DDA0',
width: 3
}
});
map.addLayer({
id: 'points',
type: 'point',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
iconImage: 'ent_i',
iconWidth: 16,
iconPriority: 100
}
});
})
</script>
</body>
</html>
Выпуклая оболочка (convex hull)
Метод turf.convex
принимает на вход объект или группу объектов и создаёт на их основе выпуклую оболочку.
const line = turf.lineString([
[34, 40],
[36, 41],
[41, 37],
[48, 42],
[42, 35],
]);
const convexHull = turf.convex(line);
Документация на метод: @turf/convex
Пример использования
Выпуклая оболочка — более точный по сравнению с граничным прямоугольником метод определения границ объекта. Обратной стороной является большая вычислительная сложность данного метода.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using @turf/convex with MapGL</title>
<meta name="description" content="Using @turf/convex with MapGL" />
<style>
html,
body,
#container {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
const line = turf.lineString([[34, 40], [36, 41], [41, 37], [48, 42], [42, 35]]);
const convexHull = turf.convex(line);
const geojson = turf.featureCollection([line, convexHull]);
const map = new mapgl.Map('container', {
center: turf.getCoord(turf.center(geojson)),
zoom: 5,
key: 'Your API access key',
});
new mapgl.GeoJsonSource(map, {
data: geojson,
attributes: {
foo: 'bar'
}
});
map.on('styleload', () => {
map.addLayer({
id: 'polygons',
type: 'polygon',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DD50'
}
});
map.addLayer({
id: 'lines',
type: 'line',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DDA0',
width: 3
}
});
map.addLayer({
id: 'points',
type: 'point',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
iconImage: 'ent_i',
iconWidth: 16,
iconPriority: 100
}
});
})
</script>
</body>
</html>
Буфер вокруг объекта (buffer)
Метод turf.buffer
позволяет пропорционально увеличить или уменьшить объект.
const line = turf.lineString([
[34, 40],
[36, 41],
[41, 37],
[48, 42],
[42, 35],
]);
const buffer = turf.buffer(line, 40);
Документация на метод: @turf/buffer
Примеры использования
- Отобразить различного рода охранные зоны, полосы отчуждения и т.п.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using @turf/buffer and MapGL</title>
<meta name="description" content="Using @turf/buffer and MapGL" />
<style>
html,
body,
#container {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
const line = turf.lineString([[34, 40], [36, 41], [41, 37], [48, 42], [42, 35]]);
const buffer = turf.buffer(line, 40);
const geojson = turf.featureCollection([line, buffer]);
const map = new mapgl.Map('container', {
center: turf.getCoord(turf.center(geojson)),
zoom: 5,
key: 'Your API access key',
});
new mapgl.GeoJsonSource(map, {
data: geojson,
attributes: {
foo: 'bar'
}
});
map.on('styleload', () => {
map.addLayer({
id: 'polygons',
type: 'polygon',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DD50'
}
});
map.addLayer({
id: 'lines',
type: 'line',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DDA0',
width: 3
}
});
map.addLayer({
id: 'points',
type: 'point',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
iconImage: 'ent_i',
iconWidth: 16,
iconPriority: 100
}
});
})
</script>
</body>
</html>
-
Показать на карте места, равноудаленные от какого-либо объекта.
Однако это работает хорошо только на малых расстояниях (до 50-100 м). При расчёте больших расстояний начинает сказываться тот факт, что
turf.buffer
рассчитывает удалённость чисто геометрически, а в действительности на кратчайшем геометрическом пути обычно содержатся препятствия: дома, реки, заборы, которые требуется обходить/объезжать. В этом случае нужно применять более точный и специализированный инструмент: изохроны.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Drawing buffers around points with @turf/buffer and MapGL</title>
<meta name="description" content="Drawing buffers around points with @turf/buffer and MapGL" />
<style>
html,
body,
#container {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
const points = [
[33.63825,34.917647],
[33.638238,34.914459],
[33.634526,34.912492],
[33.628846,34.914821],
[33.628997,34.917202],
[33.625677,34.916229]
];
const geojson = turf.featureCollection([]);
for (const pt of points) {
const feature = turf.point(pt);
geojson.features.push(feature);
geojson.features.push(turf.buffer(feature, 0.1));
}
const map = new mapgl.Map('container', {
center: turf.getCoord(turf.center(geojson)),
zoom: 15.5,
key: 'Your API access key',
});
new mapgl.GeoJsonSource(map, {
data: geojson,
attributes: {
foo: 'bar'
}
});
map.on('styleload', () => {
map.addLayer({
id: 'polygons',
type: 'polygon',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DD50'
}
});
map.addLayer({
id: 'lines',
type: 'line',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DDA0',
width: 3
}
});
map.addLayer({
id: 'points',
type: 'point',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
iconImage: 'ent_i',
iconWidth: 16,
iconPriority: 100
}
});
})
</script>
</body>
</html>
Получение центра (centroid)
Методы turf.center
, turf.centroid
и turf.centerOfMass
позволяют получить тот или иной центр объекта или группы объектов.
const polygon = turf.polygon([
[
[34, 40],
[36, 41],
[41, 37],
[48, 42],
[42, 35],
[34, 40],
],
]);
const center = turf.center(polygon);
const centroid = turf.centroid(polygon);
const centerOfMass = turf.centerOfMass(polygon);
Документация на методы:
Примеры использования
Есть 3 метода получения центра:
center
получает центр граничного прямоугольника объекта или группы.centroid
вычисляет геометрический центр объекта или группы.centerOfMass
получает центр масс фигуры.
На правильных и симметричных полигонах между этими методами практически нет разницы. В случае невыпуклых полигонов или других полигонов неправильной формы лучше всего работает centerOfMass
.
Эти методы удобны для расстановки подписей объектов.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using @turf/[center,centroid,centerOfMass] and MapGL</title>
<meta name="description" content="Using @turf/[center,centroid,centerOfMass] and MapGL" />
<style>
html,
body,
#container {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
const polygon = turf.polygon([[[34, 40], [36, 41], [41, 37], [48, 42], [42, 35], [34, 40]]]);
const center = turf.center(polygon, { properties: { name: 'Center' } });
const centroid = turf.centroid(polygon, { properties: { name: 'Centeroid' } });
const centerOfMass = turf.centerOfMass(polygon, { properties: { name: 'Center of mass' } });
const geojson = turf.featureCollection([polygon, center, centroid, centerOfMass]);
const map = new mapgl.Map('container', {
center: turf.getCoord(turf.center(geojson)),
zoom: 7.5,
key: 'Your API access key',
});
new mapgl.GeoJsonSource(map, {
data: geojson,
attributes: {
foo: 'bar'
}
});
map.on('styleload', () => {
map.addLayer({
id: 'polygons',
type: 'polygon',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DD50'
}
});
map.addLayer({
id: 'lines',
type: 'line',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DDA0',
width: 3
}
});
map.addLayer({
id: 'points',
type: 'point',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
iconImage: 'ent_i',
iconWidth: 16,
iconPriority: 100,
textFont: 'Noto_Sans',
textFontSize: 12,
textField: ['get', 'name'],
textPriority: 100
}
});
})
</script>
</body>
</html>
Ближайшая точка (nearestPoint)
Метод turf.nearestPoint
получает ближайшую точку из множества точек.
const line = turf.lineString([
[34, 40],
[36, 41],
[41, 37],
[48, 42],
[42, 35],
]);
const target = turf.point([38, 38]);
const points = turf.explode(line);
const nearest = turf.nearestPoint(target, points);
Документация на метод: @turf/nearestPoint
Особенностью метода является работа только с множеством точек. Чтобы преобразовать произвольный объект в множество точек, воспользуйтесь методом @turf/explode.
Пример использования
Найти ближайший к заданному объект.
Точка на линии (nearestPointOnLine)
Метод turf.nearestPointOnLine
получает ближайшую к заданной точку на линии или на группе линий.
const line = turf.lineString([
[34, 40],
[36, 41],
[41, 37],
[48, 42],
[42, 35],
]);
const target = turf.point([38, 38], { name: 'Target' });
const nearestOnLine = turf.nearestPointOnLine(line, target);
Документация на метод: @turf/nearestPointOnLine
Пример использования
Найти ближайшую точку на маршруте или дороге. Как и в случае с методом turf.buffer, это работает только в самых простых случаях, когда не требуется учитывать препятствия на местности. В остальных случаях воспользуйтесь API маршрутизации.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Finding nearest with Turf.js and MapGL</title>
<meta name="description" content="Finding nearest with Turf.js and MapGL" />
<style>
html,
body,
#container {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
const line = turf.lineString([[34, 40], [36, 41], [41, 37], [48, 42], [42, 35]]);
const target = turf.point([38, 38], { name: 'Target' });
const nearestOnLine = turf.nearestPointOnLine(line, target);
nearestOnLine.properties = { name: 'Nearest Point On Line' };
const points = turf.explode(line);
const nearest = turf.nearestPoint(target, points);
nearest.properties = { name: 'Nearest Point' };
const geojson = turf.featureCollection([line, target, nearest, nearestOnLine]);
const map = new mapgl.Map('container', {
center: turf.getCoord(turf.center(geojson)),
zoom: 7,
key: 'Your API access key',
});
new mapgl.GeoJsonSource(map, {
data: geojson,
attributes: {
foo: 'bar'
}
});
map.on('styleload', () => {
map.addLayer({
id: 'polygons',
type: 'polygon',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DD50'
}
});
map.addLayer({
id: 'lines',
type: 'line',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DDA0',
width: 3
}
});
map.addLayer({
id: 'points',
type: 'point',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
iconImage: 'ent_i',
iconWidth: 16,
iconPriority: 100,
textFont: 'Noto_Sans',
textFontSize: 12,
textField: ['get', 'name'],
textPriority: 100
}
});
})
</script>
</body>
</html>
Объединение (union)
Метод turf.union
объединяет несколько фигур в одну.
const points = [
[10, 10],
[12, 10],
];
const circleA = turf.buffer(turf.point([10, 10]), 300);
const circleB = turf.buffer(turf.point([12, 10]), 300);
const united = turf.union(circleA, circleB);
Документация на метод: @turf/union
Примеры использования
- Отобразить на карте область, которую составляют несколько объектов, как один объект.
- Собрать излишне фрагментированные объекты, что иногда случается при обработке геоданных.
Пересечение (intersect)
Метод turf.intersect
находит фигуру, образованную пересечением заданных фигур.
const points = [
[10, 10],
[12, 10],
];
const circleA = turf.buffer(turf.point([10, 10]), 300);
const circleB = turf.buffer(turf.point([12, 10]), 300);
const intersected = turf.intersect(circleA, circleB);
Документация на метод: @turf/intersect
Пример использования
Отобразить на карте область, являющуюся пересечением других областей. Например, бесплатную парковку в пределах заданного радиуса от объекта.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using @turf/union and @turf/intersect with MapGL</title>
<meta name="description" content="Using @turf/union and @turf/intersect with MapGL" />
<style>
html,
body,
#container {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
const points = [[10, 10], [12, 10]];
const circleA = turf.buffer(turf.point([10, 10]), 300);
const circleB = turf.buffer(turf.point([12, 10]), 300);
const united = turf.transformTranslate(turf.union(circleA, circleB), 1200, 90);
const intersected = turf.transformTranslate(turf.intersect(circleA, circleB), 1200, -90);
const geojson = turf.featureCollection([circleA, circleB, united, intersected]);
const map = new mapgl.Map('container', {
center: turf.getCoord(turf.center(geojson)),
zoom: 5,
key: 'Your API access key',
});
new mapgl.GeoJsonSource(map, {
data: geojson,
attributes: {
foo: 'bar'
}
});
map.on('styleload', () => {
map.addLayer({
id: 'polygons',
type: 'polygon',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DD50'
}
});
map.addLayer({
id: 'lines',
type: 'line',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DDA0',
width: 3
}
});
map.addLayer({
id: 'points',
type: 'point',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
iconImage: 'ent_i',
iconWidth: 16,
iconPriority: 100
}
});
})
</script>
</body>
</html>
Маска (mask)
Метод turf.mask
позволяет «вывернуть наизнанку» полигон: превратить остальное пространство вокруг него в полигон.
const poly = turf.polygon([
[
[0, 0],
[0, 2],
[2, 2],
[2, 0],
[0, 0],
],
]);
turf.mask(poly);
Документация на метод: @turf/mask
Пример использования
Акцентировать внимание на одной или нескольких областях карты и затенить остальное пространство.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using @turf/mask with MapGL</title>
<meta name="description" content="Using @turf/mask with MapGL" />
<style>
html,
body,
#container {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
const aPlace = turf.polygon([[
[105.92429031266958, -5.262701049754213],
[106.28471551378776, -5.195701693969994],
[106.64994638418318, -5.214845098030779],
[107.03920560108905, -5.305768259535483],
[107.58705190638044, -5.4158151922751046],
[107.74083332350821, -5.721926046845098],
[107.8369467063646, -6.371857054158838],
[107.82252969773305, -6.558086239663282],
[106.65475205317988, -7.569142136398696],
[106.01079236773299, -7.5834332864075975],
[105.86181661939993, -7.535794279073713],
[105.49178008359819, -7.430970061978684],
[104.78488126325243, -6.791043334337019],
[104.8233266147578, -6.456892553102861],
[105.0299703919453, -6.026947405124446],
[105.16933480114257, -5.816626441233922],
[105.30389354084993, -5.644487005567058],
[105.92429031266958, -5.262701049754213],
]]);
const bbox = turf.bbox(aPlace);
const map = new mapgl.Map('container', {
center: turf.getCoord(turf.center(aPlace)),
zoom: 7.5,
key: 'Your API access key',
});
new mapgl.GeoJsonSource(map, {
data: turf.mask(aPlace),
attributes: {
foo: 'bar'
}
});
map.on('styleload', () => {
map.addLayer({
id: 'polygons',
type: 'polygon',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: '#00B2DD50'
}
});
})
</script>
</body>
</html>
Упрощение (simplify)
Метод turf.simplify
упрощает геометрию при помощи алгоритма Рамера-Дугласа-Пекера.
const gpsTrack = turf.lineString([
[0, 0],
[0, 0.1],
[0.5, 0.5],
[1, 0.5],
]);
const simplifiedTrack = turf.simplify(gpsTrack, { tolerance: 0.2 });
// { ... geometry: { ..., coordinates: [[0, 0], [0.5, 0.5], [1, 0.5]] } }
Степень упрощения геометрии определяется параметром tolerance
, который обозначает максимальную ошибку, которую может допустить алгоритм при упрощении. Величина tolerance
имеет ту же размерность, что и исходные данные. То есть, если геокоординаты указываются в градусах, то и tolerance
будет в градусах.
Документация на метод: @turf/simplify
Примеры использования
- Получить менее сложные геометрии, которые будут отрисовываться быстрее.
- Отфильтровать данные, когда известно, что их точность не может превышать определённое значение.
- Отобразить точные данные на большом масштабе: упрощённые геометрии имеют более эстетичный внешний вид и плавные линии без визуального шума.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Using @turf/simplify with MapGL</title>
<meta name="description" content="Using @turf/simplify with MapGL" />
<style>
html,
body,
#container {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="container"></div>
<script src="https://unpkg.com/@turf/turf@6.5.0/turf.min.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<script>
const gpsTrack = turf.lineString([[0, 0], [0, 0.1], [0.5, 0.5], [1, 0.5]]);
const simplifiedTrack = turf.simplify(gpsTrack, { tolerance: 0.2 });
simplifiedTrack.properties.simplified = true;
const bbox = turf.bbox(gpsTrack);
console.log(gpsTrack, simplifiedTrack);
const geojson = turf.featureCollection([gpsTrack, simplifiedTrack]);
const map = new mapgl.Map('container', {
center: turf.getCoord(turf.center(geojson)),
zoom: 5,
key: 'Your API access key',
});
map.fitBounds(
{ southWest: bbox.slice(0, 2), northEast: bbox.slice(2, 4) },
{ padding: { left: 20, top: 20, right: 20, bottom: 20 } }
);
new mapgl.GeoJsonSource(map, {
data: geojson,
attributes: {
foo: 'bar'
}
});
map.on('styleload', () => {
map.addLayer({
id: 'lines',
type: 'line',
filter: ['==', ['sourceAttr', 'foo'], 'bar'],
style: {
color: ['match',
['get', 'simplified'],
[true], '#FF9387',
'#00B2DDA0'
],
width: ['match',
['get', 'simplified'],
[true], 3,
5
]
}
});
})
</script>
</body>
</html>