@mint-ui/map
Version:
- React map library - Control various map with one interface - Google, Naver, Kakao map supported now - Typescript supported - Canvas marker supported
1,701 lines (1,392 loc) • 205 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('tslib'), require('react'), require('classnames/bind'), require('style-inject'), require('uuid'), require('@mint-ui/tools'), require('react-dom')) :
typeof define === 'function' && define.amd ? define(['exports', 'tslib', 'react', 'classnames/bind', 'style-inject', 'uuid', '@mint-ui/tools', 'react-dom'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["@mint-ui/map"] = {}, global.tslib, global.React, global.classNames, global.styleInject, global.uuid, global.tools, global.reactDom));
})(this, (function (exports, tslib, React, classNames, styleInject, uuid, tools, reactDom) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var classNames__default = /*#__PURE__*/_interopDefaultLegacy(classNames);
var styleInject__default = /*#__PURE__*/_interopDefaultLegacy(styleInject);
var css_248z$2 = ".MintMap-module_loading-point-container__znk6l {\n display: flex;\n justify-content: center;\n align-items: center;\n width: 100%;\n height: 100%;\n}\n\n.MintMap-module_ani-blink__K89JK {\n animation: MintMap-module_blink__mqfeV infinite 0.6s;\n}\n\n@keyframes MintMap-module_blink__mqfeV {\n 0% {\n opacity: 1;\n }\n 50% {\n opacity: 0.3;\n }\n 100% {\n opacity: 1;\n }\n}\n.MintMap-module_ani-fade-in__lpHuy {\n animation: MintMap-module_fade-in__jHpv1 1s;\n}\n\n@keyframes MintMap-module_fade-in__jHpv1 {\n 0% {\n opacity: 0;\n }\n 100% {\n opacity: 1;\n }\n}\n.MintMap-module_ani-fade-out__5-esw {\n animation: MintMap-module_fade-out__CIjGe 1s;\n}\n\n@keyframes MintMap-module_fade-out__CIjGe {\n 0% {\n opacity: 1;\n }\n 100% {\n opacity: 0;\n }\n}\n.MintMap-module_ani-expansion__S2vOZ {\n animation: MintMap-module_expansion__WMo5- ease 0.6s;\n}\n\n@keyframes MintMap-module_expansion__WMo5- {\n 0% {\n width: 0%;\n }\n 100% {\n width: 100%;\n }\n}";
var styles$2 = {"loading-point-container":"MintMap-module_loading-point-container__znk6l","ani-blink":"MintMap-module_ani-blink__K89JK","blink":"MintMap-module_blink__mqfeV","ani-fade-in":"MintMap-module_ani-fade-in__lpHuy","fade-in":"MintMap-module_fade-in__jHpv1","ani-fade-out":"MintMap-module_ani-fade-out__5-esw","fade-out":"MintMap-module_fade-out__CIjGe","ani-expansion":"MintMap-module_ani-expansion__S2vOZ","expansion":"MintMap-module_expansion__WMo5-"};
styleInject__default["default"](css_248z$2);
var PositionMirror$1 =
/** @class */
function () {
function PositionMirror(lat, lng) {
this.lat = lat;
this.lng = lng;
}
return PositionMirror;
}();
var GeoCalulator =
/** @class */
function () {
function GeoCalulator() {}
GeoCalulator.computeDistanceKiloMeter = function (pos1, pos2) {
var dLat = this.deg2rad(pos2.lat - pos1.lat);
var dLon = this.deg2rad(pos2.lng - pos1.lng);
var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(this.deg2rad(pos1.lat)) * Math.cos(this.deg2rad(pos2.lat)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = this.EARTH_EQUATORIAL_RADIUS_KM * c; // Distance in km
return d;
};
GeoCalulator.deg2rad = function (deg) {
return deg * (Math.PI / 180);
};
GeoCalulator.convertMeterToLatitudeValue = function (meter) {
return meter * this.LATITUDE_POSITION_VALUE_PER_METER;
};
GeoCalulator.convertLatitudeToMeterValue = function (lat) {
return lat * this.METER_VALUE_PER_LATITUDE;
};
GeoCalulator.convertLongitudeToMeterValue = function (lat, lng) {
return lng * this.calculateLongitudeValueWithLatitudeInMeter(lat);
};
GeoCalulator.getCacheUnitOfLongitudeValueWithLatitudeInMeter = function (lat) {
return lat.toFixed(2);
};
GeoCalulator.getCacheOfLongitudeValueWithLatitudeInMeter = function (latUnit) {
return this.CACHE_OF_LNG_PER_METER.get(latUnit);
};
GeoCalulator.setCacheOfLongitudeValueWithLatitudeInMeter = function (latUnit, lngValue) {
if (this.CACHE_OF_LNG_PER_METER.size > 10) {
this.CACHE_OF_LNG_PER_METER.clear();
}
this.CACHE_OF_LNG_PER_METER.set(latUnit, lngValue);
};
GeoCalulator.calculateLongitudeValueWithLatitudeInMeter = function (lat) {
// const t = Date.now()
// Cache check
var latUnit = this.getCacheUnitOfLongitudeValueWithLatitudeInMeter(lat);
var fromCache = this.getCacheOfLongitudeValueWithLatitudeInMeter(latUnit);
if (fromCache !== undefined) {
// console.log(`cache hit!! ${Date.now() - t} ms`, fromCache, latUnit, this.CACHE_OF_LNG_PER_METER.size);
return fromCache;
} // Convert latitude and longitude to radians
var latRad = lat * Math.PI / 180; // Calculate Earth's radius at the given latitude
var radius = this.EARTH_EQUATORIAL_RADIUS * Math.sqrt(1 - Math.pow(this.EARTH_ECCENTRICITY * Math.sin(latRad), 2)); // Calculate the length of one degree of longitude in meters
var distance = 2 * Math.PI * radius * Math.cos(latRad) / 360; // Cache set
this.setCacheOfLongitudeValueWithLatitudeInMeter(latUnit, distance); // console.log(`calculated ${Date.now() - t} ms`)
return distance;
};
GeoCalulator.computeNextPositionAndDistances = function (context) {
var pos1 = context.pos1,
pos2 = context.pos2,
prevPos2 = context.prevPos2,
velocityKmh = context.velocityKmh,
prevVelocityKmh = context.prevVelocityKmh,
elapsedTimeMs = context.elapsedTimeMs; // console.log('velocityKmh / elapsedTimeMs',velocityKmh , elapsedTimeMs);
//총 가야할 거리 (km)
if (pos2 !== prevPos2) {
//목표가 바뀌면 거리 및 비율 재계산
context.totalDistance = this.computeDistanceKiloMeter(pos1, pos2);
context.currDistance = 0;
context.prevPos2 = pos2;
}
var totalDistance = context.totalDistance; // console.log('totalDistance', totalDistance);
//ms 속으로 환산
if (velocityKmh !== prevVelocityKmh) {
//속도가 바뀌면 재계산
context.vPerMs = velocityKmh / this.MS_FROM_HOUR;
context.prevVelocityKmh = velocityKmh;
}
var vPerMs = context.vPerMs; //console.log('vPerMs', vPerMs);
//실제 가는 거리 계산
var nextDistance = context.distanceRemain ? context.distanceRemain : context.currDistance + elapsedTimeMs * vPerMs; //console.log('nextDistance', nextDistance);
//목표점까지 이동 후에도 남는 거리
context.currDistance = nextDistance;
if (totalDistance < context.currDistance) {
//이동 거리가 현재 목표점을 넘어가는 경우
context.distanceRemain = context.currDistance - totalDistance;
context.nextPos = pos2;
return context;
} else {
context.distanceRemain = 0;
} //각 축으로 나가야할 비율
var ratio = nextDistance / totalDistance; //console.log('ratio', ratio);
//방향값 체크
var latCalib = pos2.lat > pos1.lat ? 1 : -1;
var lngCalib = pos2.lng > pos1.lng ? 1 : -1; //각 축에 보정된 새로운 지점 리턴
var newPos = new PositionMirror$1(pos1.lat + (pos2.lat - pos1.lat) * ratio, pos1.lng + (pos2.lng - pos1.lng) * ratio);
if ((latCalib === 1 && pos2.lat <= newPos.lat || latCalib === -1 && pos2.lat >= newPos.lat) && (lngCalib === 1 && pos2.lng <= newPos.lng || lngCalib === -1 && pos2.lng >= newPos.lng)) {
newPos = pos2;
} // console.log('newPos', newPos);
//console.log('==============================================================\n');
context.nextPos = newPos;
return context;
};
GeoCalulator.EARTH_EQUATORIAL_RADIUS = 6378137; //meter (6,378,137 m)
GeoCalulator.EARTH_EQUATORIAL_RADIUS_KM = GeoCalulator.EARTH_EQUATORIAL_RADIUS / 1000;
GeoCalulator.EARTH_ECCENTRICITY = 0.08181919;
GeoCalulator.METER_VALUE_PER_LATITUDE = 110.32 * 1000; //위도 기준 1도는 110.32km
GeoCalulator.LATITUDE_POSITION_VALUE_PER_METER = 1 / GeoCalulator.METER_VALUE_PER_LATITUDE;
GeoCalulator.CACHE_OF_LNG_PER_METER = new Map();
GeoCalulator.MS_FROM_HOUR = 60 * 60 * 1000;
return GeoCalulator;
}();
var LinePoints =
/** @class */
function () {
function LinePoints(x1, y1, x2, y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
}
return LinePoints;
}();
var PositionMirror =
/** @class */
function () {
function PositionMirror(lat, lng) {
this.lat = lat;
this.lng = lng;
}
return PositionMirror;
}();
var PolygonCalculator =
/** @class */
function () {
function PolygonCalculator() {}
PolygonCalculator.getRegionInfo = function (positions) {
var maxX, minX, maxY, minY;
for (var _i = 0, positions_1 = positions; _i < positions_1.length; _i++) {
var pos = positions_1[_i];
if (maxX === undefined || pos.lat > maxX) {
maxX = pos.lat;
}
if (minX === undefined || pos.lat < minX) {
minX = pos.lat;
}
if (maxY === undefined || pos.lng > maxY) {
maxY = pos.lng;
}
if (minY === undefined || pos.lng < minY) {
minY = pos.lng;
}
}
return {
maxLat: maxX,
minLat: minX,
maxLng: maxY,
minLng: minY,
centerLat: minX && maxX ? minX + (maxX - minX) / 2 : undefined,
centerLng: minY && maxY ? minY + (maxY - minY) / 2 : undefined
};
};
PolygonCalculator.getRegionStart = function (positions) {
var info = this.getRegionInfo(positions);
if (info.minLat && info.minLng) {
return new PositionMirror(info.minLat, info.minLng);
}
throw new Error('Calculate RegionStart Error!!!');
};
PolygonCalculator.getRegionEnd = function (positions) {
var info = this.getRegionInfo(positions);
if (info.maxLat && info.maxLng) {
return new PositionMirror(info.maxLat, info.maxLng);
}
throw new Error('Calculate RegionEnd Error!!!');
};
PolygonCalculator.getCenter = function (positions) {
var info = this.getRegionInfo(positions);
if (info.centerLat && info.centerLng) {
return new PositionMirror(info.centerLat, info.centerLng);
}
throw new Error('Calculate Center Error!!!');
};
PolygonCalculator.intersects = function (positions1, positions2) {
var lines = [];
for (var i = 0; i < positions2.length - 1; i++) {
lines.push(this.convertPositionToPoints(positions2[i], positions2[i + 1]));
}
for (var i = 0; i < positions1.length - 1; i++) {
var targetLinePoints = this.convertPositionToPoints(positions1[i], positions1[i + 1]);
if (this.findCrossPoint(lines, targetLinePoints)) {
return true;
}
}
return false;
};
PolygonCalculator.getIncludedPositions = function (polygon, position) {
var targets = Array.isArray(position) ? position : [position];
var result = [];
var maxX = Math.max.apply(Math, polygon.map(function (pos) {
return pos.lng;
})) + 1;
var lines = this.convertPolygonToLinePoints(polygon);
for (var _i = 0, targets_1 = targets; _i < targets_1.length; _i++) {
var target = targets_1[_i]; //x 축으로 긴 직선과 폴리곤 path 와의 교차 수 비교
var tempLine = this.convertPositionToPoints(target, new PositionMirror(target.lat, maxX));
var crossPoints = this.getCrossPointAll(lines, tempLine); // console.log('crossPoints',crossPoints);
if (crossPoints.length > 0 && crossPoints.length % 2 != 0) {
result.push(target);
}
}
return result;
};
PolygonCalculator.convertPolygonToLinePoints = function (polygon) {
var lines = [];
for (var i = 0; i < polygon.length; i++) {
lines.push(this.convertPositionToPoints(polygon[i], polygon[i + 1 === polygon.length ? 0 : i + 1]));
}
return lines;
};
PolygonCalculator.convertPositionToPoints = function (pos1, pos2) {
return new LinePoints(pos1.lng, pos1.lat, pos2.lng, pos2.lat);
}; //두 직선의 교점 구하기
PolygonCalculator.getCrossPoint = function (sr, tr) {
var p1 = (sr.x1 - sr.x2) * (tr.y1 - tr.y2) - (sr.y1 - sr.y2) * (tr.x1 - tr.x2); //분모로 사용됨
// p1 ==0 이면 평행선 또는 일치 이므로 제외(분모)
if (p1 != 0) {
var x = ((sr.x1 * sr.y2 - sr.y1 * sr.x2) * (tr.x1 - tr.x2) - (sr.x1 - sr.x2) * (tr.x1 * tr.y2 - tr.y1 * tr.x2)) / p1;
var y = ((sr.x1 * sr.y2 - sr.y1 * sr.x2) * (tr.y1 - tr.y2) - (sr.y1 - sr.y2) * (tr.x1 * tr.y2 - tr.y1 * tr.x2)) / p1;
if (this.toFixedPosition((x - sr.x1) * (x - sr.x2)) <= 0 && this.toFixedPosition((y - sr.y1) * (y - sr.y2)) <= 0 //교점이 1선분 위에 있고
&& this.toFixedPosition((x - tr.x1) * (x - tr.x2)) <= 0 && this.toFixedPosition((y - tr.y1) * (y - tr.y2)) <= 0 //교점이 2선분 위에 있을경우
) {
return {
x: x,
y: y
};
}
}
};
PolygonCalculator.toFixedPosition = function (n) {
return Number(n.toFixed(15));
};
PolygonCalculator.getCrossPointAll = function (sr, tr) {
var result = [];
for (var _i = 0, sr_1 = sr; _i < sr_1.length; _i++) {
var tmp = sr_1[_i];
var p = this.getCrossPoint(tmp, tr);
if (p) {
result.push(p);
}
}
return result;
};
PolygonCalculator.findCrossPoint = function (sr, tr) {
for (var _i = 0, sr_2 = sr; _i < sr_2.length; _i++) {
var tmp = sr_2[_i]; //교점이 있으면 true
if (this.getCrossPoint(tmp, tr)) {
return true;
}
}
return false;
};
PolygonCalculator.simplifyPoints = function (polygon, tolerance, _lastRepeated) {
return this.simplify(this.pathCleaning(polygon), tolerance !== undefined ? tolerance : this.TOLERANCE_NAVER_STYLE);
};
PolygonCalculator.pathCleaning = function (polygon) {
if (polygon.length < 3) {
return polygon;
}
var main = polygon[0];
var delCount = 0;
for (var i = polygon.length - 1; i >= 0; i--) {
if (main.equals(polygon[i])) {
delCount += 1;
} else {
break;
}
}
delCount > 0 && polygon.splice(polygon.length - delCount, delCount);
var out = [];
out.push.apply(out, polygon);
return out;
};
PolygonCalculator.simplify = function (points, tolerance) {
if (points.length <= 2) {
return points;
}
var dMax = 0;
var index = 0; // Find the point with the maximum distance from the line segment
var denominator = this.perpendicularDistanceDenominator(points[0], points[points.length - 1]);
for (var i = 1; i < points.length - 1; i++) {
var d = this.perpendicularDistance(points[i], points[0], points[points.length - 1], denominator);
if (d > dMax) {
dMax = d;
index = i;
}
} // If the maximum distance is greater than the tolerance, recursively simplify
if (dMax > tolerance) {
var left = this.simplify(points.slice(0, index + 1), tolerance);
var right = this.simplify(points.slice(index), tolerance); // Concatenate the simplified left and right segments
return left.slice(0, left.length - 1).concat(right);
} else {
// If the maximum distance is less than or equal to the tolerance, return the endpoints
return [points[0], points[points.length - 1]];
}
};
PolygonCalculator.perpendicularDistanceDenominator = function (lineStart, lineEnd) {
var x1 = lineStart.x,
y1 = lineStart.y;
var x2 = lineEnd.x,
y2 = lineEnd.y;
return Math.sqrt(Math.pow(y2 - y1, 2) + Math.pow(x2 - x1, 2));
}; // Calculate the perpendicular distance from a point to a line segment
PolygonCalculator.perpendicularDistance = function (point, lineStart, lineEnd, denominator) {
var x = point.x;
var y = point.y;
var x1 = lineStart.x;
var y1 = lineStart.y;
var x2 = lineEnd.x;
var y2 = lineEnd.y;
return Math.abs((y2 - y1) * x - (x2 - x1) * y + x2 * y1 - y2 * x1) / denominator;
};
PolygonCalculator.calculatePolygonSize = function (polygon, innerPolygons) {
var _this = this;
var outer = this.calculatePolygonSizeMain(polygon);
var inner = 0;
innerPolygons && innerPolygons.map(function (innerPolygon) {
inner += _this.calculatePolygonSizeMain(innerPolygon);
});
return outer - inner;
};
PolygonCalculator.calculatePolygonSizeMain = function (polygon) {
var vertices = polygon.map(function (pos) {
return {
x: GeoCalulator.convertLongitudeToMeterValue(pos.lat, pos.lng),
y: GeoCalulator.convertLatitudeToMeterValue(pos.lat)
};
});
var n = vertices.length;
var sum = 0;
for (var i = 0; i < n; i++) {
var currentVertex = vertices[i];
var nextVertex = vertices[(i + 1) % n];
sum += currentVertex.x * nextVertex.y - currentVertex.y * nextVertex.x;
}
var area = Math.abs(sum) / 2;
return area;
};
PolygonCalculator.TOLERANCE_NAVER_STYLE = 1;
PolygonCalculator.TOLERANCE_GOOGLE_STYLE = 1;
return PolygonCalculator;
}();
/**
* 좌표값
* @description 위도/경도, DOM 상의 X/Y 좌표
*/
var Position =
/** @class */
function () {
function Position(lat, lng) {
/**
* 위도
* @description 위도(latitude)
*/
this.lat = 0;
/**
* 경도
* @description 경도(longitude)
*/
this.lng = 0;
this.lat = lat;
this.lng = lng;
}
Position.equals = function (pos1, pos2) {
return pos1.lat === pos2.lat && pos1.lng === pos2.lng;
};
return Position;
}();
var Bounds =
/** @class */
function () {
function Bounds(nw, se, ne, sw) {
if (!(nw && se || ne && sw)) {
throw new Error('nw/se or ne/sw needed');
} //@ts-ignore
this.nw = nw;
this.se = se;
this.ne = ne;
this.sw = sw;
if (nw && se) {
this.covertNWSEtoNESW(nw, se);
} else if (ne && sw) {
this.covertNESWtoNWSE(ne, sw);
}
}
Bounds.fromNWSE = function (nw, se) {
return new Bounds(nw, se, undefined, undefined);
};
Bounds.fromNESW = function (ne, sw) {
return new Bounds(undefined, undefined, ne, sw);
};
Bounds.prototype.covertNWSEtoNESW = function (nw, se) {
this.ne = new Position(nw.lat, se.lng);
this.sw = new Position(se.lat, nw.lng);
};
Bounds.prototype.covertNESWtoNWSE = function (ne, sw) {
this.nw = new Position(ne.lat, sw.lng);
this.se = new Position(sw.lat, ne.lng);
};
Bounds.prototype.getCenter = function () {
return new Position(this.se.lat + (this.nw.lat - this.se.lat) / 2, this.nw.lng + (this.se.lng - this.nw.lng) / 2);
};
Bounds.prototype.includesPosition = function (pos) {
return this.nw.lng < pos.lng && this.se.lng > pos.lng && this.se.lat < pos.lat && this.nw.lat > pos.lat;
};
Bounds.prototype.getIncludedPositions = function (positions) {
var result = [];
for (var _i = 0, positions_1 = positions; _i < positions_1.length; _i++) {
var pos = positions_1[_i];
if (this.includesPosition(pos)) {
result.push(pos);
}
}
return result;
};
Bounds.prototype.includes = function (positions) {
positions = Array.isArray(positions) ? positions : [positions];
for (var _i = 0, positions_2 = positions; _i < positions_2.length; _i++) {
var pos = positions_2[_i];
if (!this.includesPosition(pos)) {
return false;
}
}
return true;
};
Bounds.prototype.includesOnlyOnePoint = function (positions) {
for (var _i = 0, positions_3 = positions; _i < positions_3.length; _i++) {
var pos = positions_3[_i];
if (this.includesPosition(pos)) {
return true;
}
}
return false;
};
Bounds.prototype.intersects = function (positions) {
return PolygonCalculator.intersects([this.nw, this.sw, this.se, this.ne, this.nw], positions);
};
return Bounds;
}();
/**
* DOM 상에서의 좌표를 표현 (픽셀을 나타내는 숫자)
*/
var Offset =
/** @class */
function () {
/**
* DOM 상에서의 좌표를 표현 (픽셀을 나타내는 숫자)
*/
function Offset(x, y) {
this.x = x;
this.y = y;
}
Offset.prototype.equals = function (other) {
return other && this.x === other.x && this.y === other.y;
};
return Offset;
}();
var Spacing =
/** @class */
function () {
function Spacing() {}
return Spacing;
}();
var MintMapControllerContext = React.createContext(null);
function MintMapProvider(_a) {
var controller = _a.controller,
children = _a.children;
return React__default["default"].createElement(MintMapControllerContext.Provider, {
value: controller
}, children);
}
function useMintMapController() {
var controller = React.useContext(MintMapControllerContext);
if (controller === null) {
throw new Error('controller is not initialized');
}
return controller;
}
var css_248z$1 = ".MintMapCore-module_mint-map-root__SMfwn {\n position: relative;\n height: 100%;\n overflow: hidden;\n}\n\n.MintMapCore-module_mint-map-container__8MIIr {\n height: 100%;\n}";
var styles$1 = {"mint-map-root":"MintMapCore-module_mint-map-root__SMfwn","mint-map-container":"MintMapCore-module_mint-map-container__8MIIr"};
styleInject__default["default"](css_248z$1);
var cn$3 = classNames__default["default"].bind(styles$1);
function MintMapCore(_a) {
var _this = this;
var onLoad = _a.onLoad,
_b = _a.visible,
visible = _b === void 0 ? true : _b,
zoomLevel = _a.zoomLevel,
center = _a.center,
_c = _a.centerMoveWithPanning,
centerMoveWithPanning = _c === void 0 ? false : _c,
children = _a.children; //controller
var controller = useMintMapController(); //맵 초기화
var elementRef = React.useRef(null);
var _d = React.useState(false),
mapInitialized = _d[0],
setMapInitialized = _d[1];
var currMapInitialized = React.useRef(false);
React.useEffect(function () {
(function () {
return tslib.__awaiter(_this, void 0, void 0, function () {
var map_1;
return tslib.__generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!(elementRef && elementRef.current)) return [3
/*break*/
, 2];
return [4
/*yield*/
, controller.initializingMap(elementRef.current)];
case 1:
map_1 = _a.sent();
if (!currMapInitialized.current) {
currMapInitialized.current = true; //onload callback (setTimeout 으로 맵이 초기화 될 텀을 준다. 특히 google map..)
setTimeout(function () {
// console.log('setMapInitialized true');
setMapInitialized(true);
onLoad && onLoad(map_1, controller);
}, 100);
}
_a.label = 2;
case 2:
return [2
/*return*/
];
}
});
});
})();
}, [controller, elementRef]); //줌레벨
React.useEffect(function () {
if (zoomLevel && controller && mapInitialized) {
var prevZoomLevel = controller === null || controller === void 0 ? void 0 : controller.getZoomLevel();
if (prevZoomLevel !== zoomLevel) {
controller === null || controller === void 0 ? void 0 : controller.setZoomLevel(zoomLevel);
}
}
}, [zoomLevel]); //센터
React.useEffect(function () {
if (center && controller && mapInitialized) {
var prevCenter = controller.getCenter();
if (!Position.equals(prevCenter, center)) {
centerMoveWithPanning ? controller === null || controller === void 0 ? void 0 : controller.panningTo(center) : controller === null || controller === void 0 ? void 0 : controller.setCenter(center);
}
}
}, [center]);
return React__default["default"].createElement("div", {
className: cn$3('mint-map-root')
}, mapInitialized && children, React__default["default"].createElement("div", {
className: cn$3('mint-map-container'),
style: {
visibility: visible ? 'inherit' : 'hidden'
},
ref: elementRef
}));
}
var AnimationPlayer =
/** @class */
function () {
function AnimationPlayer(drawFunction, fps) {
this.prevtime = 0;
this.elapsedTime = 0;
this.fps = null;
this.baseDrawGapTime = null;
this.deltaTime = 0;
this.playing = false;
this.draw = drawFunction;
this.fps = fps || null;
if (fps !== undefined) {
this.baseDrawGapTime = 1000 / fps;
}
this.init();
}
AnimationPlayer.prototype.init = function () {
this.deltaTime = 0;
this.prevtime = 0;
this.elapsedTime = 0;
this.playing = false;
};
AnimationPlayer.prototype.start = function () {
this.init();
this.resume();
};
AnimationPlayer.prototype.stop = function () {
this.playing = false;
};
AnimationPlayer.prototype.resume = function () {
this.playing = true; //@ts-ignore
window.requestAnimationFrame(this.makeFrame.bind(this));
};
AnimationPlayer.prototype.makeFrame = function (timestamp) {
//frame 간 시간 변화
if (this.prevtime === 0) {
this.prevtime = timestamp;
}
this.deltaTime += timestamp - this.prevtime; //정해진 시간이 없거나, 정해진 시간이 지났으면 draw 호출
this.prevtime = timestamp;
if (!this.baseDrawGapTime || this.baseDrawGapTime <= this.deltaTime) {
//다음 루프 준비
this.elapsedTime += this.deltaTime; //draw 콜백에서 stop 신호오면 멈춤
var stopFlag = this.draw(this.deltaTime, this.elapsedTime); //delta 초기화
this.deltaTime = 0;
if (stopFlag) {
this.stop();
}
}
if (this.playing) {
//@ts-ignore
window.requestAnimationFrame(this.makeFrame.bind(this));
}
};
return AnimationPlayer;
}();
function waiting(evaluation, timeoutSeconds) {
return tslib.__awaiter(this, void 0, void 0, function () {
var max;
return tslib.__generator(this, function (_a) {
max = (timeoutSeconds || 5) * 1000;
return [2
/*return*/
, new Promise(function (resolve) {
var start = new Date().getTime();
var inter = setInterval(function () {
//타임아웃 체크
var time = new Date().getTime();
if (time - start > max) {
clearInterval(inter);
resolve(false);
return;
} //평가식 체크
if (evaluation()) {
clearInterval(inter);
resolve(true);
}
}, 100);
})];
});
});
}
var getClusterInfo = function (basePixelSize, mapBounds, mapWidth, mapHeight, itemList, sizeFunction) {
var _a; //1. basePixelSize 기준으로 현재 지도 크기를 베이스로 영역 갯수 정하기
var rowCount = Number((mapWidth / basePixelSize).toFixed(0)) || 1;
var colCount = Number((mapHeight / basePixelSize).toFixed(0)) || 1; //console.log('rowCount', rowCount, 'colCount', colCount)
var boundsLineSizeX = Number(((mapBounds.ne.lng - mapBounds.nw.lng) / rowCount).toFixed(7));
var boundsLineSizeY = Number(((mapBounds.nw.lat - mapBounds.se.lat) / colCount).toFixed(7)); //console.log('boundsLineSize', boundsLineSizeX, boundsLineSizeY)
var boundsPos = [];
var tempX1, tempY1, tempX2, tempY2;
for (var i = 0; i < rowCount; i++) {
tempX1 = mapBounds.nw.lng + boundsLineSizeX * i;
tempX2 = mapBounds.nw.lng + boundsLineSizeX * (i + 1);
var rows = [];
boundsPos.push(rows);
for (var k = 0; k < colCount; k++) {
tempY2 = mapBounds.se.lat + boundsLineSizeY * k;
tempY1 = mapBounds.se.lat + boundsLineSizeY * (k + 1);
var thisBounds = Bounds.fromNWSE(new Position(tempY1, tempX1), new Position(tempY2, tempX2));
var includedList = thisBounds.getIncludedPositions(itemList);
rows.push({
bounds: thisBounds,
checked: false,
center: false,
centerPosition: thisBounds.getCenter(),
incList: [],
itemList: includedList,
size: basePixelSize
});
}
} //좌표마다 검사해서 인접셀 병합 처리
var centerList = [];
var totalItemCount = 0;
var min;
var max;
for (var i = 0; i < boundsPos.length; i++) {
for (var k = 0; k < boundsPos[i].length; k++) {
var curr = boundsPos[i][k];
if (curr.checked) continue;
curr.checked = true; //현재기준 8방향 객체 모으기
var incList = [];
if (boundsPos[i]) {
boundsPos[i][k - 1] && incList.push(boundsPos[i][k - 1]);
boundsPos[i][k + 1] && incList.push(boundsPos[i][k + 1]);
}
if (boundsPos[i - 1]) {
boundsPos[i - 1][k - 1] && incList.push(boundsPos[i - 1][k - 1]);
boundsPos[i - 1][k] && incList.push(boundsPos[i - 1][k]);
boundsPos[i - 1][k + 1] && incList.push(boundsPos[i - 1][k + 1]);
}
if (boundsPos[i + 1]) {
boundsPos[i + 1][k + 1] && incList.push(boundsPos[i + 1][k + 1]);
boundsPos[i + 1][k] && incList.push(boundsPos[i + 1][k]);
boundsPos[i + 1][k - 1] && incList.push(boundsPos[i + 1][k - 1]);
}
for (var _i = 0, incList_1 = incList; _i < incList_1.length; _i++) {
var inc = incList_1[_i];
if (inc.checked) continue;
inc.checked = true;
if (inc.itemList && inc.itemList.length > 0) {
curr.incList.push(inc);
(_a = curr.itemList).push.apply(_a, inc.itemList);
curr.center = true;
}
}
if (curr.center) {
centerList.push(curr);
var avrLat = calculateAverage(curr.itemList.map(function (item) {
return item.lat;
}));
var avrLng = calculateAverage(curr.itemList.map(function (item) {
return item.lng;
}));
curr.centerPosition = new Position(avrLat, avrLng);
totalItemCount += curr.itemList.length;
if (!min || curr.itemList.length < min) {
min = curr.itemList.length;
}
if (!max || curr.itemList.length > max) {
max = curr.itemList.length;
}
}
}
}
var status = {
total: totalItemCount,
average: totalItemCount / centerList.length,
min: min,
max: max
};
sizeFunction = sizeFunction || function (info, status) {
var minSize = basePixelSize / 4;
var maxSize = basePixelSize;
return Math.min(Math.max(basePixelSize * info.itemList.length / status.average, minSize), maxSize);
};
for (var _b = 0, centerList_1 = centerList; _b < centerList_1.length; _b++) {
var center = centerList_1[_b];
center.size = sizeFunction(center, status);
} // console.log('centerList', centerList, status);
return centerList;
};
var calculateAverage = function (nums) {
var sum = 0;
for (var _i = 0, nums_1 = nums; _i < nums_1.length; _i++) {
var num = nums_1[_i];
sum += num;
}
return Number((sum / nums.length).toFixed(7));
};
function log(debug, label) {
var args = [];
for (var _i = 2; _i < arguments.length; _i++) {
args[_i - 2] = arguments[_i];
}
if (!debug) return;
args && console.log.apply(console, tslib.__spreadArray(['[mint-map debug]', label || ''], args, false));
}
var MintMapStatus =
/** @class */
function () {
function MintMapStatus() {
this.marker = 0;
this.byLabel = new Map();
}
MintMapStatus.prototype.init = function () {
this.marker = 0;
this.byLabel.clear();
};
MintMapStatus.prototype.print = function () {
var str = "[mint-map status]\n\nmarker : ".concat(this.marker, "\n ");
if (this.byLabel.size > 0) {
str += '\n-------- status detail (by label) ----------';
this.byLabel.forEach(function (val, key) {
str += "\n(".concat(key, ") : ").concat(val);
});
str += '\n\n';
}
console.log(str);
};
MintMapStatus.prototype.setMarker = function (inc, label) {
this.marker += inc;
if (label) {
var curr = this.byLabel.get(label);
var calc = 0;
if (curr === undefined) {
calc = inc;
} else {
calc = curr + inc;
}
if (calc === 0) {
this.byLabel.delete(label);
} else {
this.byLabel.set(label, calc);
}
}
};
return MintMapStatus;
}();
var Status = new MintMapStatus();
function getMapOfType(mapType, map) {
if (!map) {
return undefined;
}
var base = window[mapType];
return base && map instanceof base.maps.Map ? map : undefined;
}
var MintMapController =
/** @class */
function () {
//constructor
function MintMapController(props) {
this.mapApiLoaded = false;
this.mapInitialized = false;
this.processedTime = 0; //props
this.mapProps = props;
} //기본 기능
MintMapController.prototype.getMap = function () {
return this.map;
};
MintMapController.prototype.getMapType = function () {
return this.type;
}; //기본 기능 - 좌표 변환
MintMapController.prototype.positionToOffset = function (position) {
var div = this.mapDivElement;
var w = div === null || div === void 0 ? void 0 : div.offsetWidth;
var h = div === null || div === void 0 ? void 0 : div.offsetHeight;
var bounds = this.getCurrBounds();
var maxLng = bounds.ne.lng;
var minLng = bounds.sw.lng;
var lng = Math.abs(maxLng - minLng);
var x = w * (position.lng - minLng) / lng;
var maxLat = bounds.ne.lat;
var minLat = bounds.sw.lat;
var lat = Math.abs(maxLat - minLat);
var y = h * (maxLat - position.lat) / lat;
return new Offset(x, y);
};
MintMapController.prototype.offsetToPosition = function (offset) {
var div = this.mapDivElement;
var w = div === null || div === void 0 ? void 0 : div.offsetWidth;
var h = div === null || div === void 0 ? void 0 : div.offsetHeight;
var bounds = this.getCurrBounds();
var maxLng = bounds.ne.lng;
var minLng = bounds.sw.lng;
var lng = Math.abs(maxLng - minLng); //const x = w * (position.lng - minLng) / lng
var lngVal = offset.x * lng / w + minLng;
var maxLat = bounds.ne.lat;
var minLat = bounds.sw.lat;
var lat = Math.abs(maxLat - minLat); //const y = h * (maxLat - position.lat) / lat
var latVal = (offset.y * lat / h - maxLat) * -1;
return new Position(latVal, lngVal);
}; //스크립트 로드
MintMapController.prototype.loadScript = function (url, id, checkLoaded) {
return tslib.__awaiter(this, void 0, void 0, function () {
return tslib.__generator(this, function (_a) {
//boolean -> script 로드 후 callback 실행 여부
//** callback 실행이 여기서 안된 경우는 바깥에서 해줘야한다.
return [2
/*return*/
, new Promise(function (resolve) {
//이미 로드되어 있으면 종료
if (checkLoaded()) {
resolve(false);
return;
}
var prevElement = id ? document.getElementById(id) : undefined; //기존 스크립트 로드 정보가 있으면
if (prevElement) {
// console.log(`already loaded script => ${id}`)
//로드 되어 있지 않으면 스크립트 load 이벤트 끼워넣기
prevElement.addEventListener('load', function () {
// console.log('script loaded!!!')
resolve(false);
});
return;
} //스크립트 로드 처리
var script = document.createElement('script');
script.src = url;
script.async = true;
script.defer = true;
id && (script.id = id);
script.addEventListener('load', function () {
resolve(true);
});
document.body.appendChild(script);
})];
});
});
};
MintMapController.prototype.getRandomFunctionName = function (prefix) {
return "".concat(prefix, "_").concat(uuid.v4().replace(/-/g, '_'));
};
/**
* URL 빌더 메서드
*
* @param {string} baseUrl: 기본 URL
* @param {{ [ key: string ]: string | string[] }} param: 파라미터 JSON
* @returns {string} URL
*/
MintMapController.prototype.buildUrl = function (baseUrl, param) {
var params = Object.entries(param).map(function (_a) {
var key = _a[0],
value = _a[1];
var temp = Array.isArray(value) ? value.join(',') : value;
return "".concat(key, "=").concat(temp);
}).join('&');
return "".concat(baseUrl, "?").concat(params);
};
/**
* 쓰로틀링 처리
* @returns
*/
MintMapController.prototype.checkBoundsChangeThrottleTime = function () {
if (!this.mapProps.boundsChangeThrottlingTime) return true; //boundsChangeThrottlingTime 이내의 연속 요청은 제외시킴
var time = new Date().getTime();
if (this.processedTime > 0 && time - this.processedTime < this.mapProps.boundsChangeThrottlingTime) {
return false;
} else {
this.processedTime = time;
return true;
}
};
MintMapController.prototype.getBaseToMapZoom = function (zoomBase) {
var baseMap = MapZoomInfo.BASE_TO_MAP.get(zoomBase);
if (baseMap) {
var mapZoomInfo = baseMap.get(this.getMapType());
if (mapZoomInfo) {
return mapZoomInfo.level;
}
}
throw new Error("[getBaseToMapZoom][".concat(zoomBase, "] is not valid zoom level"));
};
MintMapController.prototype.getMapToBaseZoom = function (mapZoom) {
var baseZoom = MapZoomInfo.MAP_TO_BASE.get(this.getMapType() + mapZoom);
if (baseZoom) {
return baseZoom;
}
throw new Error("[getMapToBaseZoom][".concat(mapZoom, "] is not valid zoom level"));
};
MintMapController.prototype.morph = function (position, zoom, option) {
var naverMap = getMapOfType('naver', this.map);
if (naverMap) {
naverMap.morph(position, zoom, tslib.__assign({}, option));
} else {
// 센터로 이동
this.setCenter(position); // 줌 이동
this.setZoomLevel(zoom);
}
};
MintMapController.prototype.printStatus = function () {
Status.print();
};
return MintMapController;
}();
var MapZoomInfo =
/** @class */
function () {
function MapZoomInfo() {}
MapZoomInfo.BASE_TO_MAP = new Map([[1, new Map([['google', {
level: 1
}], ['naver', {
level: 6
}], ['kakao', {
level: 14
}]])], [2, new Map([['google', {
level: 2,
distance: 2000,
unit: 'km'
}], ['naver', {
level: 6
}], ['kakao', {
level: 14
}]])], [3, new Map([['google', {
level: 3,
distance: 1000,
unit: 'km'
}], ['naver', {
level: 6
}], ['kakao', {
level: 14
}]])], [4, new Map([['google', {
level: 4,
distance: 500,
unit: 'km'
}], ['naver', {
level: 6
}], ['kakao', {
level: 14
}]])], [5, new Map([['google', {
level: 5,
distance: 200,
unit: 'km'
}], ['naver', {
level: 6
}], ['kakao', {
level: 14
}]])], [6, new Map([['google', {
level: 6,
distance: 100,
unit: 'km'
}], ['naver', {
level: 6
}], ['kakao', {
level: 14
}]])], [7, new Map([['google', {
level: 7,
distance: 50,
unit: 'km'
}], ['naver', {
level: 7
}], ['kakao', {
level: 13
}]])], [8, new Map([['google', {
level: 8,
distance: 20,
unit: 'km'
}], ['naver', {
level: 8
}], ['kakao', {
level: 12
}]])], [9, new Map([['google', {
level: 9,
distance: 10,
unit: 'km'
}], ['naver', {
level: 9
}], ['kakao', {
level: 11
}]])], [10, new Map([['google', {
level: 10,
distance: 5,
unit: 'km'
}], ['naver', {
level: 10
}], ['kakao', {
level: 10
}]])], [11, new Map([['google', {
level: 11,
distance: 2,
unit: 'km'
}], ['naver', {
level: 11
}], ['kakao', {
level: 9
}]])], [12, new Map([['google', {
level: 12,
distance: 1,
unit: 'km'
}], ['naver', {
level: 12
}], ['kakao', {
level: 8
}]])], [13, new Map([['google', {
level: 13,
distance: 500,
unit: 'm'
}], ['naver', {
level: 13
}], ['kakao', {
level: 7
}]])], [14, new Map([['google', {
level: 14,
distance: 500,
unit: 'm'
}], ['naver', {
level: 14
}], ['kakao', {
level: 6
}]])], [15, new Map([['google', {
level: 15,
distance: 500,
unit: 'm'
}], ['naver', {
level: 15
}], ['kakao', {
level: 5
}]])], [16, new Map([['google', {
level: 16,
distance: 500,
unit: 'm'
}], ['naver', {
level: 16
}], ['kakao', {
level: 4
}]])], [17, new Map([['google', {
level: 17,
distance: 500,
unit: 'm'
}], ['naver', {
level: 17
}], ['kakao', {
level: 3
}]])], [18, new Map([['google', {
level: 18,
distance: 500,
unit: 'm'
}], ['naver', {
level: 18
}], ['kakao', {
level: 2
}]])], [19, new Map([['google', {
level: 19,
distance: 500,
unit: 'm'
}], ['naver', {
level: 19
}], ['kakao', {
level: 1
}]])], [20, new Map([['google', {
level: 20,
distance: 500,
unit: 'm'
}], ['naver', {
level: 20
}], ['kakao', {
level: 1
}]])], [21, new Map([['google', {
level: 21,
distance: 500,
unit: 'm'
}], ['naver', {
level: 21
}], ['kakao', {
level: 1
}]])], [22, new Map([['google', {
level: 22,
distance: 500,
unit: 'm'
}], ['naver', {
level: 21
}], ['kakao', {
level: 1
}]])]]);
MapZoomInfo.MAP_TO_BASE = new Map(Array.from(MapZoomInfo.BASE_TO_MAP.entries()).map(function (item) {
var base = item[0];
var mapZoom = item[1];
var result = [];
mapZoom.forEach(function (value, key) {
result.push([key + value.level, base]);
});
return result;
}).flatMap(function (a) {
return a;
}));
return MapZoomInfo;
}();
// export type MapEvent = 'bounds_changed'|'center_changed'|'idle'|'zoom_changed'|'zoomstart'
// export type MapUIEvent = 'click'|'dblclick'|''
var MapEvent =
/** @class */
function () {
function MapEvent() {
this.BOUNDS_CHANGED = 'bounds_changed';
this.CENTER_CHANGED = 'center_changed';
this.IDLE = 'idle';
this.ZOOM_CHANGED = 'zoom_changed';
this.ZOOMSTART = 'zoomstart';
}
MapEvent.prototype.get = function (eventName) {
var value = this[eventName];
if (typeof value === 'string') {
return value;
}
};
return MapEvent;
}();
var MapUIEvent =
/** @class */
function () {
function MapUIEvent() {
this.CLICK = 'click';
this.DBLCLICK = 'dblclick';
this.MOUSEDOWN = 'mousedown';
this.MOUSEUP = 'mouseup';
this.MOUSEOUT = 'mouseout';
this.MOUSEMOVE = 'mousemove';
this.MOUSEOVER = 'mouseover';
this.DRAG = 'drag';
this.DRAGSTART = 'dragstart';
this.DRAGEND = 'dragend';
this.RIGHTCLICK = 'rightclick';
this.CONTEXTMENU = 'contextmenu';
}
MapUIEvent.prototype.get = function (eventName) {
var value = this[eventName];
if (typeof value === 'string') {
return value;
}
};
return MapUIEvent;
}();
var MintMapCanvasRenderer =
/** @class */
function () {
function MintMapCanvasRenderer(context) {
this.context = context;
}
return MintMapCanvasRenderer;
}();
function SVGCircle(_a) {
var _b = _a.radius,
radius = _b === void 0 ? 100 : _b,
_c = _a.background,
background = _c === void 0 ? 'lightgreen' : _c,
children = _a.children,
_d = _a.svgProperties,
svgProperties = _d === void 0 ? {} : _d,
_e = _a.shapeProperties,
shapeProperties = _e === void 0 ? {} : _e;
var _f = React.useState(radius * 2),
boxSize = _f[0],
setBoxSize = _f[1];
React.useEffect(function () {
// console.log('SVGCircle radius', radius);
setBoxSize(radius * 2);
}, [radius]);
return React__default["default"].createElement(React__default["default"].Fragment, null, React__default["default"].createElement("svg", tslib.__assign({
pointerEvents: "none",
width: boxSize,
height: boxSize,
viewBox: "0 0 ".concat(boxSize, " ").concat(boxSize),
overflow: 'visible'
}, svgProperties), React__default["default"].createElement("circle", tslib.__assign({
pointerEvents: "visiblepainted",
cx: radius,
cy: radius,
r: radius,
fill: background
}, shapeProperties))), React__default["default"].createElement("div", {
style: {
pointerEvents: 'none',
position: 'absolute',
left: '0px',
top: '0px',
width: "".concat(boxSize, "px"),
height: "".concat(boxSize, "px")
}
}, children));
}
var Drawable =
/** @class */
function () {
function Drawable() {}
return Drawable;
}();
var Marker =
/** @class */
function (_super) {
tslib.__extends(Marker, _super);
/**
* 지도에 표시할 마커정보
*/
function Marker(options) {
var _this = _super.call(this) || this;
_this.options = options;
return _this;
}
return Marker;
}(Drawable);
var Polyline =
/** @class */
function (_super) {
tslib.__extends(Polyline, _super);
/**
* 지도에 표시할 폴리곤정보
*/
function Polyline(options) {
var _this = _super.call(this) || this;
_this.options = options;
return _this;
}
return Polyline;
}(Drawable);
var Polygon =
/** @class */
function (_super) {
tslib.__extends(Polygon, _super);
/**
* 지도에 표시할 폴리곤정보
*/
function Polygon(options) {
var _this = _super.call(this) || this;
_this.options = options;
return _this;
}
/**
* 폴리곤의 중점을 구한다.
*/
Polygon.prototype.getCenter = function () {
if (Array.isArray(this.options.position) && this.options.position.length > 0) {
var paths = this.options.position.map(function (elem) {
return elem instanceof Position ? elem : new Position(elem[0], elem[1]);
});
return PolygonCalculator.getCenter(paths);
}
throw new Error('center 를 찾을 수 없습니다.');
};
return Polygon;
}(Drawable);
function SVGPolygon(_a) {
var path = _a.path,
_b = _a.innerPath,
innerPath = _b === void 0 ? [] : _b,
_c = _a.background,
background = _c === void 0 ? 'lightblue' : _c,
_d = _a.svgProperties,
svgProperties = _d === void 0 ? {} : _d,
_e = _a.shapeProperties,
shapeProperties = _e === void 0 ? {} : _e,
_f = _a.mode,
mode = _f === void 0 ? 'POLYGON' : _f,
children = _a.children;
var getPolygonInfo = React.useCallback(function (path) {
var maxX, minX, maxY, minY;
for (var _i = 0, path_1 = path; _i < path_1.length; _i++) {
var offset = path_1[_i];
if (maxX === undefined || offset.x > maxX) {
maxX = offset.x;
}
if (minX === undefined || offset.x < minX) {
minX = offset.x;
}
if (maxY === undefined || offset.y > maxY) {
maxY = offset.y;
}
if (minY === undefined || offset.y < minY) {
minY = offset.y;
}
}
if (!maxX || !minX || !maxY || !minY) {
return {
containerLeft: 0,
containerTop: 0,
containerWidth: 0,
containerHeight: 0
};
}
var width = maxX - minX;
var height = maxY - minY;
return {
containerLeft: minX,
containerTop: minY,
containerWidth: width,
containerHeight: height
};
}, []);
var getD = React.useCallback(function (path, innerPath, mode) {
var isPolygon = mode === 'POLYGON';
var out = ''; //path
out += getLine(path, isPolygon); //inner path
for (var _i = 0, innerPath_1 = innerPath; _i < innerPath_1.length; _i++) {
var inner = innerPath_1[_i];
out += ' ' + getLine(inner, isPolygon);
}
return out;
}, []);
var getLine = React.useCallback(function (path, isPolygon) {
return path.map(function (offset, idx) {
if (idx === 0) {
return "M ".concat(offset.x, ",").concat(offset.y);
} else if (idx === 1) {
return "L ".concat(offset.x, ",").concat(offset.y);
} else {
if (offset.equals(path[idx - 1])) {
return '';
} else {
return "".concat(offset.x, ",").concat(offset.y);
}
}
}).join(' ')