ansuko 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +343 -0
- package/README.md +353 -0
- package/dist/index.d.ts +196 -0
- package/dist/index.js +506 -0
- package/dist/plugins/geo.d.ts +25 -0
- package/dist/plugins/geo.js +566 -0
- package/dist/plugins/ja.d.ts +13 -0
- package/dist/plugins/ja.js +149 -0
- package/dist/plugins/prototype.d.ts +22 -0
- package/dist/plugins/prototype.js +9 -0
- package/dist/util.d.ts +23 -0
- package/dist/util.js +104 -0
- package/dist/vitest.config.d.ts +2 -0
- package/dist/vitest.config.js +8 -0
- package/package.json +84 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import GeoJSON from "geojson";
|
|
2
|
+
import { type AnsukoType } from "../index.js";
|
|
3
|
+
/**
|
|
4
|
+
* Geometry type selector for conversions. Use `auto` to try higher dimensions first.
|
|
5
|
+
*/
|
|
6
|
+
export declare enum GeomType {
|
|
7
|
+
point = 0,
|
|
8
|
+
polygon = 1,
|
|
9
|
+
lineString = 2,
|
|
10
|
+
multiPoint = 3,
|
|
11
|
+
multiPolygon = 4,
|
|
12
|
+
multiLineString = 5,
|
|
13
|
+
auto = "auto"
|
|
14
|
+
}
|
|
15
|
+
export interface AnsukoGeoPluginExtension {
|
|
16
|
+
toPointGeoJson: (geo: any, digit?: number) => GeoJSON.Point | null;
|
|
17
|
+
toPolygonGeoJson: (geo: any, digit?: number) => GeoJSON.Polygon | null;
|
|
18
|
+
toLineStringGeoJson: (geo: any, digit?: number) => GeoJSON.LineString | null;
|
|
19
|
+
toMultiPointGeoJson: (geo: any, digit?: number) => GeoJSON.MultiPoint | null;
|
|
20
|
+
toMultiPolygonGeoJson: (geo: any, digit?: number) => GeoJSON.MultiPolygon | null;
|
|
21
|
+
toMultiLineStringGeoJson: (geo: any, digit?: number) => GeoJSON.MultiLineString | null;
|
|
22
|
+
unionPolygon: (geo: any, digit?: number) => GeoJSON.Polygon | GeoJSON.MultiPolygon | null;
|
|
23
|
+
}
|
|
24
|
+
declare const ansukoGeoPlugin: <T extends AnsukoType>(ansuko: T) => T & AnsukoGeoPluginExtension;
|
|
25
|
+
export default ansukoGeoPlugin;
|
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
import * as turf from "@turf/turf";
|
|
2
|
+
/**
|
|
3
|
+
* Geometry type selector for conversions. Use `auto` to try higher dimensions first.
|
|
4
|
+
*/
|
|
5
|
+
export var GeomType;
|
|
6
|
+
(function (GeomType) {
|
|
7
|
+
GeomType[GeomType["point"] = 0] = "point";
|
|
8
|
+
GeomType[GeomType["polygon"] = 1] = "polygon";
|
|
9
|
+
GeomType[GeomType["lineString"] = 2] = "lineString";
|
|
10
|
+
GeomType[GeomType["multiPoint"] = 3] = "multiPoint";
|
|
11
|
+
GeomType[GeomType["multiPolygon"] = 4] = "multiPolygon";
|
|
12
|
+
GeomType[GeomType["multiLineString"] = 5] = "multiLineString";
|
|
13
|
+
GeomType["auto"] = "auto";
|
|
14
|
+
})(GeomType || (GeomType = {}));
|
|
15
|
+
const ansukoGeoPlugin = (ansuko) => {
|
|
16
|
+
const _ = ansuko;
|
|
17
|
+
/**
|
|
18
|
+
* Converts a coordinate-like value to a [lng, lat] tuple, optionally rounding digits.
|
|
19
|
+
* Swaps order if lat/lng appear to be inverted. Returns null when invalid.
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
22
|
+
const toLngLatToArray = (coord, digit) => {
|
|
23
|
+
if (_.isNil(coord)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
let tLat = null;
|
|
27
|
+
let tLng = null;
|
|
28
|
+
if (Array.isArray(coord) && _.isNumber(coord[0]) && _.isNumber(coord[1])) {
|
|
29
|
+
tLng = _.toNumber(coord[0]);
|
|
30
|
+
tLat = _.toNumber(coord[1]);
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
if (typeof coord !== "object") {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
tLng = _.toNumber(coord.lng ?? coord.lon ?? coord.longitude);
|
|
37
|
+
tLat = _.toNumber(coord.lat ?? coord.latitude);
|
|
38
|
+
}
|
|
39
|
+
if (!tLat || !tLng) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
if (((tLat < -90 || tLat > 90) && (tLng > -90 && tLng < 90)) ||
|
|
43
|
+
((tLng < -180 || tLng > 180) && (tLat > -180 && tLat < 180))) {
|
|
44
|
+
const t = tLat;
|
|
45
|
+
tLat = tLng;
|
|
46
|
+
tLat = t;
|
|
47
|
+
}
|
|
48
|
+
return [
|
|
49
|
+
(_.isNumber(digit) ? _.toNumber(tLng.toFixed(digit)) : tLng),
|
|
50
|
+
(_.isNumber(digit) ? _.toNumber(tLat.toFixed(digit)) : tLat)
|
|
51
|
+
];
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Converts coordinates or an object to a Point GeoJSON.
|
|
55
|
+
* @param geo - [lng,lat] or {lat,lng}
|
|
56
|
+
* @param digit - Rounding digits
|
|
57
|
+
* @returns Point or null
|
|
58
|
+
* @example toPointGeoJson([139.7671,35.6812])
|
|
59
|
+
* @example toPointGeoJson({ lat:35.6895, lng:139.6917 })
|
|
60
|
+
* @category Geo Utilities
|
|
61
|
+
*/
|
|
62
|
+
const toPointGeoJson = (geo, digit) => {
|
|
63
|
+
let lngLat = null;
|
|
64
|
+
if (_.isEmpty(geo)) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
if (Array.isArray(geo)) {
|
|
68
|
+
if (_.size(geo) === 1) {
|
|
69
|
+
lngLat = toLngLatToArray(geo[0], digit);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
lngLat = toLngLatToArray(geo, digit);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (_.has(geo, "lat") || _.has(geo, "latitude")) {
|
|
76
|
+
lngLat = toLngLatToArray(geo, digit);
|
|
77
|
+
}
|
|
78
|
+
else if (_.has(geo, "type")) {
|
|
79
|
+
switch (geo.type.toLowerCase()) {
|
|
80
|
+
case "featurecollection":
|
|
81
|
+
if (_.get(geo, "features[0].geometry.type")?.toLowerCase() !== "point") {
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
if (_.size(geo.features) !== 1) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
lngLat = toLngLatToArray(_.get(geo, "features[0].geometry.coordinates"), digit);
|
|
88
|
+
break;
|
|
89
|
+
case "feature":
|
|
90
|
+
if (geo.geometry?.type?.toLowerCase() !== "point") {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
lngLat = toLngLatToArray(geo.geometry?.coordinates, digit);
|
|
94
|
+
break;
|
|
95
|
+
case "point":
|
|
96
|
+
lngLat = toLngLatToArray(geo.coordinates, digit);
|
|
97
|
+
break;
|
|
98
|
+
default:
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
if (!lngLat) {
|
|
106
|
+
return null;
|
|
107
|
+
} // null チェック追加
|
|
108
|
+
try {
|
|
109
|
+
return turf.point(lngLat)?.geometry;
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* Converts an outer ring to Polygon GeoJSON (ring must be closed).
|
|
117
|
+
* @param geo - [[lng,lat], ...] or Polygon-like GeoJSON
|
|
118
|
+
* @param digit - Rounding digits
|
|
119
|
+
* @returns Polygon or null
|
|
120
|
+
* @example toPolygonGeoJson([
|
|
121
|
+
* [139.70,35.68],[139.78,35.68],[139.78,35.75],[139.70,35.75],[139.70,35.68]
|
|
122
|
+
* ])
|
|
123
|
+
* @category Geo Utilities
|
|
124
|
+
*/
|
|
125
|
+
const toPolygonGeoJson = (geo, digit) => {
|
|
126
|
+
let ll = null;
|
|
127
|
+
if (_.arrayDepth(geo) === 3 && _.size(geo) === 1) { // [[外周リング]]
|
|
128
|
+
ll = _.first(geo).map((coord) => toLngLatToArray(coord, digit));
|
|
129
|
+
}
|
|
130
|
+
else if (_.arrayDepth(geo) === 2) { // [外周リング]
|
|
131
|
+
ll = geo.map((coord) => toLngLatToArray(coord, digit));
|
|
132
|
+
}
|
|
133
|
+
else if (_.has(geo, "type")) {
|
|
134
|
+
switch (_.get(geo, "type")?.toLowerCase()) {
|
|
135
|
+
case "featurecollection":
|
|
136
|
+
if (_.get(geo, "features[0].geometry.type")?.toLowerCase() !== "polygon") {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
if (_.size(geo.features) !== 1) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
// 最初のリング(外周)だけ取得
|
|
143
|
+
ll = _.first(_.first(geo.features)?.geometry.coordinates)
|
|
144
|
+
?.map((coord) => toLngLatToArray(coord, digit));
|
|
145
|
+
break;
|
|
146
|
+
case "feature":
|
|
147
|
+
if (geo.geometry?.type?.toLowerCase() !== "polygon") {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
ll = _.first(geo.geometry.coordinates)?.map((coord) => toLngLatToArray(coord, digit));
|
|
151
|
+
break;
|
|
152
|
+
case "polygon":
|
|
153
|
+
ll = _.first(geo.coordinates)?.map((coord) => toLngLatToArray(coord, digit));
|
|
154
|
+
break;
|
|
155
|
+
default:
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
if (!ll || ll.find(_.isEmpty)) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
if (!_.isEqual(_.first(ll), _.last(ll))) {
|
|
166
|
+
return null;
|
|
167
|
+
} // 閉じてるかチェック
|
|
168
|
+
try {
|
|
169
|
+
return turf.polygon([ll])?.geometry; // [ll] で囲む(外周リングの配列にする)
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Converts coordinate sequence to LineString GeoJSON; returns null if self-intersecting.
|
|
177
|
+
* @param geo - [[lng,lat], ...] or LineString-like GeoJSON
|
|
178
|
+
* @param digit - Rounding digits
|
|
179
|
+
* @returns LineString or null
|
|
180
|
+
* @example toLineStringGeoJson([[139.70,35.68],[139.75,35.70],[139.80,35.72]])
|
|
181
|
+
* @category Geo Utilities
|
|
182
|
+
*/
|
|
183
|
+
const toLineStringGeoJson = (geo, digit) => {
|
|
184
|
+
let ll = null;
|
|
185
|
+
if (_.arrayDepth(geo) === 3 && _.size(geo) === 1) {
|
|
186
|
+
ll = _.first(geo).map((l) => toLngLatToArray(l, digit));
|
|
187
|
+
}
|
|
188
|
+
else if (_.arrayDepth(geo) === 2) {
|
|
189
|
+
ll = geo.map((l) => toLngLatToArray(l, digit));
|
|
190
|
+
}
|
|
191
|
+
else if (_.has(geo, "type")) {
|
|
192
|
+
switch (_.get(geo, "type")?.toLowerCase()) {
|
|
193
|
+
case "featurecollection":
|
|
194
|
+
if (_.get(geo, "features[0].geometry.type")?.toLowerCase() !== "linestring") {
|
|
195
|
+
return null;
|
|
196
|
+
}
|
|
197
|
+
if (_.size(geo.features) !== 1) {
|
|
198
|
+
return null;
|
|
199
|
+
}
|
|
200
|
+
ll = _.first(geo.features)?.geometry.coordinates?.map((l) => toLngLatToArray(l, digit));
|
|
201
|
+
break;
|
|
202
|
+
case "feature":
|
|
203
|
+
if (geo.geometry?.type?.toLowerCase() !== "linestring") {
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
ll = geo.geometry?.coordinates?.map((l) => toLngLatToArray(l, digit));
|
|
207
|
+
break;
|
|
208
|
+
case "linestring":
|
|
209
|
+
ll = geo.coordinates?.map((l) => toLngLatToArray(l, digit));
|
|
210
|
+
break;
|
|
211
|
+
default:
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
if (!ll || ll.find(_.isEmpty)) {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
try {
|
|
222
|
+
const feature = turf.lineString(ll); // Feature を取得
|
|
223
|
+
if (!_.isEmpty(turf.kinks(feature)?.features)) {
|
|
224
|
+
return null;
|
|
225
|
+
} // 交差チェック
|
|
226
|
+
return feature.geometry; // Geometry を返す
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
/**
|
|
233
|
+
* Converts multiple points to MultiPoint GeoJSON.
|
|
234
|
+
* @param geo - [[lng,lat], ...] or MultiPoint-like GeoJSON
|
|
235
|
+
* @param digit - Rounding digits
|
|
236
|
+
* @returns MultiPoint or null
|
|
237
|
+
* @example toMultiPointGeoJson([[139.70,35.68],[139.71,35.69],[139.72,35.70]])
|
|
238
|
+
* @category Geo Utilities
|
|
239
|
+
*/
|
|
240
|
+
const toMultiPointGeoJson = (geo, digit) => {
|
|
241
|
+
let ll = null;
|
|
242
|
+
if (_.arrayDepth(geo) === 2) { // MultiPointは2次元
|
|
243
|
+
ll = geo.map((coord) => toLngLatToArray(coord, digit));
|
|
244
|
+
}
|
|
245
|
+
else if (_.has(geo, "type")) {
|
|
246
|
+
switch (_.get(geo, "type")?.toLowerCase()) {
|
|
247
|
+
case "featurecollection":
|
|
248
|
+
if (_.get(geo, "features[0].geometry.type")?.toLowerCase() !== "multipoint") {
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
if (_.size(geo.features) !== 1) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
ll = _.first(geo.features).geometry?.coordinates
|
|
255
|
+
?.map((coord) => toLngLatToArray(coord, digit));
|
|
256
|
+
break;
|
|
257
|
+
case "feature":
|
|
258
|
+
if (geo.geometry?.type?.toLowerCase() !== "multipoint") {
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
ll = geo.geometry?.coordinates?.map((coord) => toLngLatToArray(coord, digit));
|
|
262
|
+
break;
|
|
263
|
+
case "multipoint":
|
|
264
|
+
ll = geo.coordinates?.map((coord) => toLngLatToArray(coord, digit));
|
|
265
|
+
break;
|
|
266
|
+
default:
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
return null;
|
|
272
|
+
}
|
|
273
|
+
if (!ll || ll.find(_.isEmpty)) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
try {
|
|
277
|
+
return turf.multiPoint(ll)?.geometry;
|
|
278
|
+
}
|
|
279
|
+
catch {
|
|
280
|
+
return null;
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
/**
|
|
284
|
+
* Converts polygons (outer rings) to MultiPolygon GeoJSON.
|
|
285
|
+
* @param geo - Polygons
|
|
286
|
+
* @param digit - Rounding digits
|
|
287
|
+
* @returns MultiPolygon or null
|
|
288
|
+
* @example toMultiPolygonGeoJson([
|
|
289
|
+
* [[[139.7,35.6],[139.8,35.6],[139.8,35.7],[139.7,35.7],[139.7,35.6]]],
|
|
290
|
+
* [[[139.75,35.65],[139.85,35.65],[139.85,35.75],[139.75,35.75],[139.75,35.65]]]
|
|
291
|
+
* ])
|
|
292
|
+
* @category Geo Utilities
|
|
293
|
+
*/
|
|
294
|
+
const toMultiPolygonGeoJson = (geo, digit) => {
|
|
295
|
+
let ll = null;
|
|
296
|
+
if (_.arrayDepth(geo) === 4) {
|
|
297
|
+
ll = geo.map((polygon) => _.first(polygon).map((l) => toLngLatToArray(l, digit)));
|
|
298
|
+
}
|
|
299
|
+
else if (_.has(geo, "type")) {
|
|
300
|
+
switch (geo.type.toLowerCase()) {
|
|
301
|
+
case "featurecollection":
|
|
302
|
+
if (_.get(geo, "features[0].geometry.type")?.toLowerCase() !== "multipolygon") {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
if (_.size(geo.features) !== 1) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
ll = _.first(geo.features).geometry?.coordinates?.map((polygon) => _.first(polygon).map((l) => toLngLatToArray(l, digit)));
|
|
309
|
+
break;
|
|
310
|
+
case "feature":
|
|
311
|
+
if (geo.geometry?.type?.toLowerCase() !== "multipolygon") {
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
ll = geo.geometry?.coordinates?.map((polygon) => _.first(polygon).map((l) => toLngLatToArray(l, digit)));
|
|
315
|
+
break;
|
|
316
|
+
case "multipolygon":
|
|
317
|
+
ll = geo.coordinates?.map((polygon) => _.first(polygon).map((l) => toLngLatToArray(l, digit)));
|
|
318
|
+
break;
|
|
319
|
+
default:
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
else {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
if (!ll || ll.find((polygon) => !polygon || polygon.find((ring) => !ring || ring.find(_.isEmpty)) || !_.isEqual(_.first(polygon), _.last(polygon)))) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
return turf.multiPolygon(ll)?.geometry;
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
/**
|
|
337
|
+
* Converts lines to MultiLineString GeoJSON, rejecting self-intersections.
|
|
338
|
+
* @param geo - Lines
|
|
339
|
+
* @param digit - Rounding digits
|
|
340
|
+
* @returns MultiLineString or null
|
|
341
|
+
* @example toMultiLineStringGeoJson([
|
|
342
|
+
* [[139.7,35.6],[139.8,35.65]],
|
|
343
|
+
* [[139.75,35.62],[139.85,35.68]]
|
|
344
|
+
* ])
|
|
345
|
+
* @category Geo Utilities
|
|
346
|
+
*/
|
|
347
|
+
const toMultiLineStringGeoJson = (geo, digit) => {
|
|
348
|
+
let ll = null;
|
|
349
|
+
if (_.arrayDepth(geo) === 3) {
|
|
350
|
+
ll = geo.map((line) => line.map((l) => toLngLatToArray(l, digit)));
|
|
351
|
+
}
|
|
352
|
+
else if (_.has(geo, "type")) {
|
|
353
|
+
switch (_.get(geo, "type").toLowerCase()) {
|
|
354
|
+
case "featurecollection":
|
|
355
|
+
if (_.get(geo, "features[0].geometry.type")?.toLowerCase() !== "multilinestring") {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
if (_.size(geo.features) !== 1) {
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
ll = _.first(geo.features).geometry?.coordinates?.map((line) => line.map((l) => toLngLatToArray(l, digit)));
|
|
362
|
+
break;
|
|
363
|
+
case "feature":
|
|
364
|
+
if (geo.geometry?.type?.toLowerCase() !== "multilinestring") {
|
|
365
|
+
return null;
|
|
366
|
+
} // 修正
|
|
367
|
+
ll = geo.geometry?.coordinates?.map((line) => line.map((l) => toLngLatToArray(l, digit)));
|
|
368
|
+
break;
|
|
369
|
+
case "multilinestring":
|
|
370
|
+
ll = geo.coordinates?.map((line) => line.map((l) => toLngLatToArray(l, digit)));
|
|
371
|
+
break;
|
|
372
|
+
default:
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
if (!ll || ll.find((g) => !g || g.find(_.isEmpty))) {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
try {
|
|
380
|
+
// 一旦linestringでチェック
|
|
381
|
+
if (ll.find((l) => {
|
|
382
|
+
const r = turf.lineString(l);
|
|
383
|
+
if (!r)
|
|
384
|
+
return true;
|
|
385
|
+
const kinks = turf.kinks(r);
|
|
386
|
+
return !_.isEmpty(kinks.features);
|
|
387
|
+
})) {
|
|
388
|
+
return null;
|
|
389
|
+
}
|
|
390
|
+
return turf.multiLineString(ll)?.geometry;
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
/**
|
|
397
|
+
* Unions polygons into a single Polygon/MultiPolygon.
|
|
398
|
+
* @param geo - Polygon/MultiPolygon/FeatureCollection, etc.
|
|
399
|
+
* @param digit - Rounding digits
|
|
400
|
+
* @returns Unified geometry or null
|
|
401
|
+
* @example unionPolygon([
|
|
402
|
+
* [[139.7,35.6],[139.8,35.6],[139.8,35.7],[139.7,35.7],[139.7,35.6]],
|
|
403
|
+
* [[139.75,35.65],[139.85,35.65],[139.85,35.75],[139.75,35.75],[139.75,35.65]]
|
|
404
|
+
* ])
|
|
405
|
+
* @category Geo Utilities
|
|
406
|
+
*/
|
|
407
|
+
const unionPolygon = (geo, digit) => {
|
|
408
|
+
let list = null;
|
|
409
|
+
const g = geo;
|
|
410
|
+
if (_.arrayDepth(geo) === 4) {
|
|
411
|
+
geo = _.first(geo);
|
|
412
|
+
}
|
|
413
|
+
if (Array.isArray(geo)) {
|
|
414
|
+
list = geo.map(g => {
|
|
415
|
+
const p = toPolygonGeoJson(g, digit);
|
|
416
|
+
return p ? turf.polygon(p.coordinates) : null;
|
|
417
|
+
}).filter(Boolean);
|
|
418
|
+
}
|
|
419
|
+
else if (_.has(geo, "type")) {
|
|
420
|
+
switch (_.get(g, "type")?.toLowerCase()) {
|
|
421
|
+
case "featurecollection":
|
|
422
|
+
list = g.features?.map((f) => {
|
|
423
|
+
const p = toPolygonGeoJson(f, digit);
|
|
424
|
+
return p ? turf.polygon(p.coordinates) : null;
|
|
425
|
+
}).filter(Boolean);
|
|
426
|
+
break;
|
|
427
|
+
case "feature":
|
|
428
|
+
if (g.geometry?.type !== "polygon") {
|
|
429
|
+
return g;
|
|
430
|
+
}
|
|
431
|
+
else if (g.geometry?.type === "multipolygon") {
|
|
432
|
+
list = g.geometry?.coordinates.map((c) => {
|
|
433
|
+
const p = toPolygonGeoJson(c, digit);
|
|
434
|
+
return p ? turf.polygon(p.coordinates) : null;
|
|
435
|
+
}).filter(Boolean);
|
|
436
|
+
}
|
|
437
|
+
break;
|
|
438
|
+
case "polygon":
|
|
439
|
+
return g;
|
|
440
|
+
case "multipolygon":
|
|
441
|
+
list = g.coordinates.map((c) => {
|
|
442
|
+
const p = toPolygonGeoJson(c, digit);
|
|
443
|
+
return p ? turf.polygon(p.coordinates) : null;
|
|
444
|
+
}).filter(Boolean);
|
|
445
|
+
break;
|
|
446
|
+
default:
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
else {
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
if (_.isEmpty(list)) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
if (_.size(list) === 1) {
|
|
457
|
+
return _.first(list).geometry;
|
|
458
|
+
}
|
|
459
|
+
return turf.union(turf.featureCollection(list))?.geometry ?? null;
|
|
460
|
+
};
|
|
461
|
+
/**
|
|
462
|
+
* Converts input to GeoJSON geometry of the given type. `auto` tries higher dimensions first.
|
|
463
|
+
* @param geo - Input
|
|
464
|
+
* @param type - GeomType
|
|
465
|
+
* @param digit - Rounding digits
|
|
466
|
+
* @returns Geometry or null
|
|
467
|
+
* @example toGeoJson([139.7,35.6], GeomType.point)
|
|
468
|
+
* @example toGeoJson([[139.7,35.6],[139.8,35.7]], GeomType.lineString)
|
|
469
|
+
* @example toGeoJson(
|
|
470
|
+
* [[[139.7,35.6],[139.8,35.6],[139.8,35.7],[139.7,35.7],[139.7,35.6]]],
|
|
471
|
+
* GeomType.polygon
|
|
472
|
+
* )
|
|
473
|
+
* @category Geo Utilities
|
|
474
|
+
*/
|
|
475
|
+
const toGeoJson = (geo, type = GeomType.auto, digit) => {
|
|
476
|
+
if (_.isEmpty(geo)) {
|
|
477
|
+
return null;
|
|
478
|
+
}
|
|
479
|
+
if (typeof geo === "string") {
|
|
480
|
+
geo = _.parseJSON(geo);
|
|
481
|
+
}
|
|
482
|
+
switch (type) {
|
|
483
|
+
case GeomType.point:
|
|
484
|
+
return toPointGeoJson(geo, digit);
|
|
485
|
+
case GeomType.polygon:
|
|
486
|
+
return toPolygonGeoJson(geo, digit);
|
|
487
|
+
case GeomType.lineString:
|
|
488
|
+
return toLineStringGeoJson(geo, digit);
|
|
489
|
+
case GeomType.multiLineString:
|
|
490
|
+
return toMultiLineStringGeoJson(geo, digit);
|
|
491
|
+
case GeomType.multiPoint:
|
|
492
|
+
return toMultiPointGeoJson(geo, digit);
|
|
493
|
+
case GeomType.multiPolygon:
|
|
494
|
+
return toMultiPolygonGeoJson(geo, digit);
|
|
495
|
+
default:
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
// auto: 次元の高い順に試す
|
|
499
|
+
return toMultiPolygonGeoJson(geo, digit)
|
|
500
|
+
?? toMultiLineStringGeoJson(geo, digit)
|
|
501
|
+
?? toPolygonGeoJson(geo, digit)
|
|
502
|
+
?? toLineStringGeoJson(geo, digit)
|
|
503
|
+
?? toMultiPointGeoJson(geo, digit)
|
|
504
|
+
?? toPointGeoJson(geo, digit);
|
|
505
|
+
};
|
|
506
|
+
/**
|
|
507
|
+
* Converts inputs into a list of GeoJSON.Features suitable for TerraDraw.
|
|
508
|
+
* Multi-geometries are exploded into individual features and assigned UUIDs.
|
|
509
|
+
* @param geo - Geometry/Feature/FeatureCollection or nested arrays
|
|
510
|
+
* @returns Feature array (may be empty when input is invalid)
|
|
511
|
+
*/
|
|
512
|
+
const parseToTerraDraw = (geo) => {
|
|
513
|
+
let feature = toGeoJson(geo, GeomType.auto);
|
|
514
|
+
if (_.isEmpty(feature) && Array.isArray(geo)) {
|
|
515
|
+
return geo.flatMap(parseToTerraDraw);
|
|
516
|
+
}
|
|
517
|
+
if (!feature)
|
|
518
|
+
return [];
|
|
519
|
+
const features = [];
|
|
520
|
+
const geom = feature.geometry; // 一度だけ
|
|
521
|
+
switch (geom.type) {
|
|
522
|
+
case "MultiPoint":
|
|
523
|
+
geom.coordinates.forEach((coord) => {
|
|
524
|
+
const f = turf.point(coord, { mode: "point" });
|
|
525
|
+
f.id = crypto.randomUUID();
|
|
526
|
+
features.push(f);
|
|
527
|
+
});
|
|
528
|
+
break;
|
|
529
|
+
case "MultiLineString":
|
|
530
|
+
geom.coordinates.forEach((coords) => {
|
|
531
|
+
const f = turf.lineString(coords, { mode: "linestring" });
|
|
532
|
+
f.id = crypto.randomUUID();
|
|
533
|
+
features.push(f);
|
|
534
|
+
});
|
|
535
|
+
break;
|
|
536
|
+
case "MultiPolygon":
|
|
537
|
+
geom.coordinates.forEach((coords) => {
|
|
538
|
+
const f = turf.polygon(coords, { mode: "polygon" });
|
|
539
|
+
f.id = crypto.randomUUID();
|
|
540
|
+
features.push(f);
|
|
541
|
+
});
|
|
542
|
+
break;
|
|
543
|
+
default:
|
|
544
|
+
const f = { ...feature };
|
|
545
|
+
f.id = crypto.randomUUID();
|
|
546
|
+
f.properties = {
|
|
547
|
+
...f.properties,
|
|
548
|
+
mode: geom.type.toLowerCase()
|
|
549
|
+
};
|
|
550
|
+
features.push(f);
|
|
551
|
+
}
|
|
552
|
+
return features;
|
|
553
|
+
};
|
|
554
|
+
const a = ansuko;
|
|
555
|
+
a.toGeoJson = toGeoJson;
|
|
556
|
+
a.toPointGeoJson = toPointGeoJson;
|
|
557
|
+
a.toPolygonGeoJson = toPolygonGeoJson;
|
|
558
|
+
a.toLineStringGeoJson = toLineStringGeoJson;
|
|
559
|
+
a.toMultiPointGeoJson = toMultiPointGeoJson;
|
|
560
|
+
a.toMultiLineStringGeoJson = toMultiLineStringGeoJson;
|
|
561
|
+
a.toMultiPolygonGeoJson = toMultiPolygonGeoJson;
|
|
562
|
+
a.unionPolygon = unionPolygon;
|
|
563
|
+
a.parseToTerraDraw = parseToTerraDraw;
|
|
564
|
+
return ansuko;
|
|
565
|
+
};
|
|
566
|
+
export default ansukoGeoPlugin;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AnsukoType } from "../index.js";
|
|
2
|
+
import { toHalfWidth, haifun } from "../util.js";
|
|
3
|
+
export interface AnsukoJaExtension {
|
|
4
|
+
kanaToFull: (str: unknown) => string | null;
|
|
5
|
+
kanaToHalf: (str: unknown) => string | null;
|
|
6
|
+
kanaToHira: (str: unknown) => string | null;
|
|
7
|
+
hiraToKana: (str: unknown) => string | null;
|
|
8
|
+
toHalfWidth: typeof toHalfWidth;
|
|
9
|
+
toFullWidth: (value: unknown, withHaifun?: string) => string | null;
|
|
10
|
+
haifun: typeof haifun;
|
|
11
|
+
}
|
|
12
|
+
declare const ansukoJaPlugin: <T extends AnsukoType>(ansuko: T) => T & AnsukoJaExtension;
|
|
13
|
+
export default ansukoJaPlugin;
|