PageSourceSearch

https://proparcel.com/static/js/core/utils/coordinate_utils.js

js proparcel.com collected 2026-09-24 14:51:28 UTC 13,349 bytes, 423 lines download raw bytes

1/**
2 * Coordinate Utils - Core Module
3 * Handles coordinate transformations and validations
4 */
5
6class CoordinateUtils {
7    constructor() {
8        // Turkey coordinate bounds
9        this.TURKEY_BOUNDS = {
10            minLon: 25.0,
11            maxLon: 45.0,
12            minLat: 36.0,
13            maxLat: 43.0
14        };
15
16        // Layers to normalize by default (swap lat/lon)
17        this.normalizedLayerIds = new Set([]);
18    }
19
20    /**
21     * Detect coordinate order [lon,lat] or [lat,lon]
22     */
23    detectCoordinateOrder(coord) {
24        if (!coord || coord.length !== 2) {
25            return "unknown";
26        }
27        
28        const [a, b] = coord;
29        const aNum = Number(a);
30        const bNum = Number(b);
31
32        // Türkiye için sıkı sınırlar ile önce belirlemeye çalış
33        const isLonLatTR = aNum >= this.TURKEY_BOUNDS.minLon && aNum <= this.TURKEY_BOUNDS.maxLon &&
34                           bNum >= this.TURKEY_BOUNDS.minLat && bNum <= this.TURKEY_BOUNDS.maxLat;
35        const isLatLonTR = aNum >= this.TURKEY_BOUNDS.minLat && aNum <= this.TURKEY_BOUNDS.maxLat &&
36                           bNum >= this.TURKEY_BOUNDS.minLon && bNum <= this.TURKEY_BOUNDS.maxLon;
37        if (isLonLatTR && !isLatLonTR) return "lonlat";
38        if (isLatLonTR && !isLonLatTR) return "latlon";
39
40        // Genel küresel aralıklarla ikincil kontrol
41        const looksLonLat = Math.abs(aNum) <= 180 && Math.abs(bNum) <= 90;
42        const looksLatLon = Math.abs(aNum) <= 90 && Math.abs(bNum) <= 180;
43        if (looksLonLat && !looksLatLon) return "lonlat";
44        if (looksLatLon && !looksLonLat) return "latlon";
45
46        // İkisi de mümkün görünüyorsa lon,lat varsay
47        return "lonlat";
48    }
49
50    /**
51     * Convert coordinates to [lat, lon] format for Leaflet
52     */
53    toLatLng(coords) {
54        if (!Array.isArray(coords) || coords.length !== 2) {
55            throw new Error('Invalid coordinates format');
56        }
57
58        const order = this.detectCoordinateOrder(coords);
59        if (order === "lonlat") {
60            return [coords[1], coords[0]]; // [lat, lon]
61        } else if (order === "latlon") {
62            return [coords[0], coords[1]]; // [lat, lon]
63        } else {
64            throw new Error('Unable to detect coordinate order');
65        }
66    }
67
68    /**
69     * Convert coordinates to [lon, lat] format for Mapbox
70     */
71    toLngLat(coords) {
72        if (!Array.isArray(coords) || coords.length !== 2) {
73            throw new Error('Invalid coordinates format');
74        }
75
76        const order = this.detectCoordinateOrder(coords);
77        if (order === "lonlat") {
78            return [coords[0], coords[1]]; // [lon, lat]
79        } else if (order === "latlon") {
80            return [coords[1], coords[0]]; // [lon, lat]
81        } else {
82            throw new Error('Unable to detect coordinate order');
83        }
84    }
85
86    /**
87     * Convert coordinate array to Leaflet format
88     */
89    coordsToLatLngs(coords) {
90        if (!Array.isArray(coords)) return [];
91        
92        if (Array.isArray(coords[0]) && Array.isArray(coords[0][0])) {
93            // Polygon rings - use exterior ring
94            return coords[0].map(pt => this.toLatLng(pt));
95        }
96        
97        return coords.map(pt => this.toLatLng(pt));
98    }
99
100    /**
101     * Convert coordinate array to Mapbox format
102     */
103    coordsToLngLats(coords) {
104        if (!Array.isArray(coords)) return [];
105        
106        if (Array.isArray(coords[0]) && Array.isArray(coords[0][0])) {
107            // Polygon rings - use exterior ring
108            return coords[0].map(pt => this.toLngLat(pt));
109        }
110        
111        return coords.map(pt => this.toLngLat(pt));
112    }
113
114    /**
115     * Extract polygons from GeoJSON
116     */
117    extractPolygonsFromGeoJSON(geojson) {
118        // Extracting polygons from geojson
119        const polys = [];
120        
121        const pushGeom = (geom) => {
122            if (!geom) {
123                return;
124            }
125            
126            if (geom.type === "Polygon") {
127                polys.push(this.coordsToLatLngs(geom.coordinates));
128            } else if (geom.type === "MultiPolygon") {
129                (geom.coordinates || []).forEach(rings => {
130                    polys.push(this.coordsToLatLngs(rings));
131                });
132            } else if (geom.type === "LineString") {
133                polys.push((geom.coordinates || []).map(pt => this.toLatLng(pt)));
134            }
135        };
136
137        if (!geojson) return polys;
138        
139        if (geojson.type === "FeatureCollection") {
140            (geojson.features || []).forEach(f => pushGeom(f && f.geometry));
141        } else if (geojson.type === "Feature") {
142            pushGeom(geojson.geometry);
143        } else if (geojson.type) {
144            pushGeom(geojson);
145        } else if (Array.isArray(geojson)) {
146            polys.push(this.coordsToLatLngs(geojson));
147        }
148        
149        return polys;
150    }
151
152    /**
153     * Build GeoJSON FeatureCollection for Mapbox
154     */
155    buildGeoJSONFeatureCollection(data) {
156        // Building FeatureCollection from data
157        const fc = { type: "FeatureCollection", features: [] };
158        const qb = data && data.quarter_border;
159        
160        if (!qb) {
161            return fc;
162        }
163
164        const addFeature = (geom) => {
165            if (!geom) {
166                return;
167            }
168            // Adding feature with geometry
169            fc.features.push({ type: "Feature", properties: {}, geometry: geom });
170        };
171
172        if (qb.type === "FeatureCollection") {
173            // Processing as FeatureCollection
174            (qb.features || []).forEach((f, index) => {
175                // Processing feature
176                addFeature(f && f.geometry);
177            });
178        } else if (qb.type === "Feature") {
179            // Processing as Feature
180            addFeature(qb.geometry);
181        } else if (qb.type === "Polygon" || qb.type === "MultiPolygon" || qb.type === "LineString") {
182            // Processing as Geometry
183            addFeature(qb);
184        } else if (Array.isArray(qb)) {
185            // Processing as Array
186            addFeature({ type: "Polygon", coordinates: [qb] });
187        } else {
188        }
189        
190        // Final FeatureCollection created
191        return fc;
192    }
193
194    /**
195     * Validate coordinates are within Turkey bounds
196     */
197    validateTurkeyCoordinates(coords) {
198        if (!Array.isArray(coords) || coords.length !== 2) {
199            return false;
200        }
201
202        const [lon, lat] = this.toLngLat(coords);
203        
204        return (lon >= this.TURKEY_BOUNDS.minLon && lon <= this.TURKEY_BOUNDS.maxLon &&
205                lat >= this.TURKEY_BOUNDS.minLat && lat <= this.TURKEY_BOUNDS.maxLat);
206    }
207
208    /**
209     * Calculate distance between two points (Haversine formula)
210     */
211    calculateDistance(lat1, lon1, lat2, lon2) {
212        const R = 6371; // Earth's radius in kilometers
213        const dLat = this.toRadians(lat2 - lat1);
214        const dLon = this.toRadians(lon2 - lon1);
215        
216        const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
217                  Math.cos(this.toRadians(lat1)) * Math.cos(this.toRadians(lat2)) *
218                  Math.sin(dLon / 2) * Math.sin(dLon / 2);
219        
220        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
221        return R * c;
222    }
223
224    /**
225     * Convert degrees to radians
226     */
227    toRadians(degrees) {
228        return degrees * (Math.PI / 180);
229    }
230
231    /**
232     * Convert radians to degrees
233     */
234    toDegrees(radians) {
235        return radians * (180 / Math.PI);
236    }
237
238    /**
239     * Calculate center point of multiple coordinates
240     */
241    calculateCenter(coords) {
242        if (!Array.isArray(coords) || coords.length === 0) {
243            return null;
244        }
245
246        let sumLat = 0;
247        let sumLon = 0;
248
249        coords.forEach(coord => {
250            const [lat, lon] = this.toLatLng(coord);
251            sumLat += lat;
252            sumLon += lon;
253        });
254
255        return [sumLat / coords.length, sumLon / coords.length];
256    }
257
258    /**
259     * Calculate bounding box
260     */
261    calculateBoundingBox(coords) {
262        if (!Array.isArray(coords) || coords.length === 0) {
263            return null;
264        }
265
266        let minLat = Infinity;
267        let maxLat = -Infinity;
268        let minLon = Infinity;
269        let maxLon = -Infinity;
270
271        coords.forEach(coord => {
272            const [lat, lon] = this.toLatLng(coord);
273            minLat = Math.min(minLat, lat);
274            maxLat = Math.max(maxLat, lat);
275            minLon = Math.min(minLon, lon);
276            maxLon = Math.max(maxLon, lon);
277        });
278
279        return {
280            north: maxLat,
281            south: minLat,
282            east: maxLon,
283            west: minLon
284        };
285    }
286
287    /**
288     * Format coordinates for display
289     */
290    formatCoordinates(lat, lon, precision = 6) {
291        const latStr = lat.toFixed(precision);
292        const lonStr = lon.toFixed(precision);
293        return `${latStr}, ${lonStr}`;
294    }
295
296    /**
297     * Parse coordinate string
298     */
299    parseCoordinateString(str) {
300        if (!str || typeof str !== 'string') {
301            return null;
302        }
303
304        const parts = str.split(',').map(s => s.trim());
305        if (parts.length !== 2) {
306            return null;
307        }
308
309        const lat = parseFloat(parts[0]);
310        const lon = parseFloat(parts[1]);
311
312        if (isNaN(lat) || isNaN(lon)) {
313            return null;
314        }
315
316        return [lat, lon];
317    }
318
319    /**
320     * Simple deep swap helper for arrays of coordinates
321     */
322    _swapPairsDeep(input) {
323        const isPair = (p) => Array.isArray(p) && p.length === 2 && isFinite(p[0]) && isFinite(p[1]);
324        const swap = (p) => isPair(p) ? [p[1], p[0]] : p;
325        const walk = (node) => Array.isArray(node) ? node.map(el => Array.isArray(el) ? walk(el) : swap(el)) : node;
326        return walk(input);
327    }
328
329    /**
330     * Swap lat/lon pairs inside a WKT string
331     * Supports: POINT, LINESTRING, POLYGON, MULTI*, GEOMETRYCOLLECTION
332     */
333    swapLatLonInWKT(wkt) { return wkt; }
334
335    /**
336     * Convert basic WKT to GeoJSON Geometry/Feature
337     */
338    wktToGeoJSON(wkt) { return null; }
339
340    /**
341     * Normalize any geometry-like input to swap lat/lon order.
342     * - GeoJSON Feature/FeatureCollection/Geometry
343     * - Arrays of coordinates
344     * - WKT strings (returns WKT string with swapped pairs)
345     * This is a simple swap; no CRS detection.
346     */
347    normalizeGeometrySimple(input) {
348        if (!input) return input;
349
350        // WKT string
351        if (typeof input === 'string') {
352            const upper = input.trim().toUpperCase();
353            if (/(POINT|LINESTRING|POLYGON|MULTI|GEOMETRYCOLLECTION)/.test(upper)) {
354                return this.swapLatLonInWKT(input);
355            }
356            return input;
357        }
358
359        // GeoJSON Feature
360        if (input && input.type === 'Feature') {
361            const geom = input.geometry || null;
362            const swapped = geom && geom.coordinates ? this._swapPairsDeep(geom.coordinates) : geom;
363            return { type: 'Feature', properties: input.properties || {}, geometry: geom ? { ...geom, coordinates: swapped } : geom };
364        }
365
366        // GeoJSON FeatureCollection
367        if (input && input.type === 'FeatureCollection') {
368            const feats = (input.features || []).map(f => this.normalizeGeometrySimple(f));
369            return { type: 'FeatureCollection', features: feats };
370        }
371
372        // GeoJSON Geometry
373        if (input && typeof input.type === 'string' && input.coordinates) {
374            return { type: input.type, coordinates: this._swapPairsDeep(input.coordinates) };
375        }
376
377        // Array of coordinates
378        if (Array.isArray(input)) {
379            return this._swapPairsDeep(input);
380        }
381
382        // Unknown type => return as-is
383        return input;
384    }
385
386    /**
387     * Decide if a layer should be normalized (swap lat/lon)
388     */
389    shouldNormalizeLayer(layerId) {
390        return !!(layerId && this.normalizedLayerIds.has(layerId));
391    }
392
393    /**
394     * Normalize input for a given layer (no-op if layer not enabled)
395     */
396    normalizeForLayer(layerId, input) {
397        if (!this.shouldNormalizeLayer(layerId)) return input;
398        return input;
399    }
400
401    /**
402     * Manage layer normalization list
403     */
404    setNormalizeLayers(layerIds) {
405            this.normalizedLayerIds = new Set(Array.isArray(layerIds) ? layerIds : []);
406    }
407
408    addNormalizeLayer(layerId) {
409        try { if (layerId) this.normalizedLayerIds.add(layerId); } catch(_) {}
410    }
411
412    removeNormalizeLayer(layerId) {
413        try { if (layerId) this.normalizedLayerIds.delete(layerId); } catch(_) {}
414    }
415}
416
417// Create global instance
418window.coordinateUtils = new CoordinateUtils();
419
420// Export for module systems
421if (typeof module !== 'undefined' && module.exports) {
422    module.exports = CoordinateUtils;
423}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.