Работа с интерактивной графикой в Google Maps JavaScript API не ограничивается созданием фигур. Существенная часть реальных приложений связана с последующим редактированием уже размещённых объектов: перемещением вершин, изменением формы, корректировкой координат, ограничением допустимых изменений и синхронизацией с внешним хранилищем.
Редактируемыми объектами выступают полигоны, ломаные линии,
окружности и прямоугольники. Каждый из них опирается на собственную
модель данных, но общие принципы взаимодействия унифицированы через
свойства editable, draggable и систему событий
изменения геометрии.
Любой графический объект в карте может быть переведён в режим редактирования через параметр конфигурации:
const polygon = new google.maps.Polygon({
paths: coordinates,
editable: true,
draggable: true,
map: map
});
Ключевые свойства:
Для разных типов объектов поведение отличается:
Основой является MVCArray, представляющий динамический
массив координат:
const path = polygon.getPath();
MVCArray поддерживает реактивные изменения, и любые
правки сразу отражаются на карте.
Основные методы:
getAt(index) — получение точкиsetAt(index, latLng) — замена точкиinsertAt(index, latLng) — вставкаremoveAt(index) — удалениеРедактирование сопровождается событиями, позволяющими отслеживать каждое изменение структуры объекта.
set_at — изменение существующей вершиныinsert_at — добавление новой вершиныremove_at — удаление вершиныconst path = polygon.getPath();
path.addListener("set_at", () => {
console.log("вершина изменена");
});
path.addListener("insert_at", () => {
console.log("добавлена вершина");
});
path.addListener("remove_at", () => {
console.log("вершина удалена");
});
Эти события позволяют строить реактивную модель, в которой любые изменения сразу синхронизируются с сервером или локальным состоянием приложения.
При включённом draggable объект может перемещаться как
единое целое без изменения формы.
polygon.setOptions({
draggable: true
});
События перемещения:
google.maps.event.addListener(polygon, "dragend", () => {
console.log("объект перемещён");
});
Внутри процесса перетаскивания также доступны:
dragstartdragdragendОкружность имеет два редактируемых параметра:
const circle = new google.maps.Circle({
center: { lat: 50, lng: 30 },
radius: 1000,
editable: true,
map: map
});
События:
circle.addListener("center_changed", () => {});
circle.addListener("radius_changed", () => {});
Редактирование осуществляется через изменение границ:
const rectangle = new google.maps.Rectangle({
bounds: {
north: 50,
south: 49,
east: 31,
west: 29
},
editable: true,
map: map
});
Событие изменения:
rectangle.addListener("bounds_changed", () => {});
Для создания и последующего редактирования используется
DrawingManager:
const drawingManager = new google.maps.drawing.DrawingManager({
drawingMode: null,
drawingControl: true,
polygonOptions: {
editable: true,
draggable: true
}
});
drawingManager.setMap(map);
После создания объекта его можно перевести в режим редактирования:
drawingManager.setDrawingMode(null);
polygon.setEditable(true);
Редактируемые объекты требуют постоянного отслеживания изменений и обновления внешнего состояния.
function getPolygonCoordinates(polygon) {
const path = polygon.getPath();
const coordinates = [];
for (let i = 0; i < path.getLength(); i++) {
const latLng = path.getAt(i);
coordinates.push({
lat: latLng.lat(),
lng: latLng.lng()
});
}
return coordinates;
}
function attachAutoSync(polygon) {
const path = polygon.getPath();
const sync = () => {
const data = getPolygonCoordinates(polygon);
saveToServer(data);
};
path.addListener("set_at", sync);
path.addListener("insert_at", sync);
path.addListener("remove_at", sync);
}
const bounds = new google.maps.LatLngBounds(
{ lat: 49, lng: 28 },
{ lat: 51, lng: 32 }
);
polygon.setOptions({
editable: true
});
Дополнительно ограничения реализуются вручную через проверку
координат при событиях set_at.
path.addListener("set_at", (index) => {
const point = path.getAt(index);
if (!bounds.contains(point)) {
path.setAt(index, lastValidPoint);
}
});
Редактирование геометрии требует хранения истории изменений.
const history = [];
let historyIndex = -1;
function pushState(path) {
const snapshot = [];
for (let i = 0; i < path.getLength(); i++) {
const p = path.getAt(i);
snapshot.push({ lat: p.lat(), lng: p.lng() });
}
history.push(snapshot);
historyIndex++;
}
function restoreState(polygon, state) {
const path = polygon.getPath();
path.clear();
state.forEach(coord => {
path.push(new google.maps.LatLng(coord.lat, coord.lng));
});
}
Редактируемые объекты часто синхронизируются через GeoJSON.
function polygonToGeoJSON(polygon) {
return {
type: "Feature",
geometry: {
type: "Polygon",
coordinates: [
polygon.getPath().getArray().map(p => [p.lng(), p.lat()])
]
}
};
}
function geoJSONToPolygon(map, geojson) {
const coords = geojson.geometry.coordinates[0];
const path = coords.map(c =>
new google.maps.LatLng(c[1], c[0])
);
return new google.maps.Polygon({
paths: path,
editable: true,
map: map
});
}
Изменение формы требует постоянного пересчёта метрик.
const area = google.maps.geometry.spherical.computeArea(polygon.getPath());
const length = google.maps.geometry.spherical.computeLength(polyline.getPath());
Такие вычисления часто привязываются к событиям изменения, обеспечивая динамическое отображение параметров.
Редактируемые объекты могут менять стиль в процессе взаимодействия:
polygon.setOptions({
fillOpacity: 0.4,
strokeWeight: 3
});
При начале редактирования:
polygon.addListener("dragstart", () => {
polygon.setOptions({ fillOpacity: 0.2 });
});
При завершении:
polygon.addListener("dragend", () => {
polygon.setOptions({ fillOpacity: 0.4 });
});
Редактируемые геометрии в многопользовательской среде требуют стратегии согласования:
Пример формирования diff:
function diffPaths(oldPath, newPath) {
return {
added: newPath.filter(p => !oldPath.includes(p)),
removed: oldPath.filter(p => !newPath.includes(p))
};
}
При активном редактировании возникает проблема накопления погрешности.
Типовые меры:
function roundLatLng(latLng, precision = 6) {
return {
lat: Number(latLng.lat().toFixed(precision)),
lng: Number(latLng.lng().toFixed(precision))
};
}
Редактируемые полигоны могут становиться самопересекающимися. Для предотвращения используются проверки:
Интерфейс часто связан с событиями карты:
path.addListener("insert_at", updateUI);
path.addListener("set_at", updateUI);
path.addListener("remove_at", updateUI);
Функция updateUI может пересчитывать:
В сложных приложениях переключение режимов выполняется централизованно:
function setEditMode(polygon, enabled) {
polygon.setEditable(enabled);
polygon.setDraggable(enabled);
}
Дополнительно может применяться блокировка:
MVCArray оптимизирован для реактивных обновлений, но при
массовых изменениях предпочтительно:
google.maps.event.clearListeners(path);
path.clear();
// массовое заполнение
Типичные проблемы:
Стратегии: