Skip to content

Commit 6f7b9dc

Browse files
committed
react-leafletに移行
1 parent be93c86 commit 6f7b9dc

5 files changed

Lines changed: 593 additions & 616 deletions

File tree

package-lock.json

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
"leaflet": "^1.9.4",
3333
"react": "^19.2.4",
3434
"react-dom": "^19.2.4",
35+
"react-leaflet": "^5.0.0",
3536
"react-router-dom": "^7.14.0"
3637
}
3738
}

src/geolocation.ts

Lines changed: 89 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { useEffect, useState } from "react";
12
import L from "leaflet";
23

34
// Numeric tolerance for detecting singular matrices / collinear points
@@ -164,7 +165,7 @@ function solveNormalEquations(A: number[][], b: number[]): number[] | null {
164165
// Supports 4+ reference points for better accuracy
165166
function createPolynomialTransformer(
166167
points: ReferencePoint[],
167-
): ((lat: number, lng: number) => L.LatLngExpression | null) | null {
168+
): ((lat: number, lng: number) => L.LatLngTuple | null) | null {
168169
if (points.length < 4) {
169170
console.warn(
170171
"Less than 4 reference points. Falling back to affine transformation.",
@@ -211,7 +212,7 @@ function createPolynomialTransformer(
211212
return null;
212213
}
213214

214-
return (lat: number, lng: number): L.LatLngExpression | null => {
215+
return (lat: number, lng: number): L.LatLngTuple | null => {
215216
const xCoeff0 = xCoeffs[0];
216217
const xCoeff1 = xCoeffs[1];
217218
const xCoeff2 = xCoeffs[2];
@@ -264,7 +265,7 @@ function createPolynomialTransformer(
264265
// Function to calculate affine transformation coefficients from 3 points
265266
function createAffineTransformer(
266267
points: ReferencePoint[],
267-
): ((lat: number, lng: number) => L.LatLngExpression | null) | null {
268+
): ((lat: number, lng: number) => L.LatLngTuple | null) | null {
268269
if (points.length < 3) {
269270
console.error("At least 3 reference points are required");
270271
return null;
@@ -321,7 +322,7 @@ function createAffineTransformer(
321322
p3.y * (p1.lat * p2.lng - p2.lat * p1.lng)) /
322323
det;
323324

324-
return (lat: number, lng: number): L.LatLngExpression => {
325+
return (lat: number, lng: number): L.LatLngTuple => {
325326
const x = A * lat + B * lng + C;
326327
const y = D * lat + E * lng + F;
327328
return [y, x];
@@ -336,175 +337,104 @@ export interface GeolocationOptions {
336337
debugPosition?: [number, number];
337338
}
338339

339-
export function setupGeolocation(
340-
map: L.Map,
340+
export function useGeolocation(
341341
imgWidth: number,
342342
imgHeight: number,
343-
options?: GeolocationOptions,
343+
{ debugPosition }: GeolocationOptions,
344344
) {
345-
let userMarker: L.Marker | null = null;
346-
let userCircle: L.Circle | null = null;
347-
let watchId: number | null = null;
348-
let hasAlerted = false;
349-
350-
// デバッグ用固定位置がある場合はそれを使用
351-
if (options?.debugPosition) {
352-
const [imgY, imgX] = options.debugPosition;
353-
placeOrUpdateMarker(map, [imgY, imgX], 0, () => {
354-
userMarker = null;
355-
userCircle = null;
356-
});
357-
return {
358-
cleanup: () => {},
359-
};
360-
}
345+
const [position, setPosition] = useState<[number, number] | null>(null);
346+
const [accuracy, setAccuracy] = useState<number>(0);
361347

362-
// Get location information
363-
watchId = navigator.geolocation.watchPosition(
364-
(position: GeolocationPosition) => {
365-
const userLat: number = position.coords.latitude;
366-
const userLng: number = position.coords.longitude;
367-
const accuracy = position.coords.accuracy;
368-
369-
// Convert latitude/longitude to image coordinates
370-
if (!convertLatLngToImageXY) {
371-
console.error("Coordinate transformer is not available");
372-
if (!hasAlerted) {
373-
alert("座標変換システムの初期化に失敗しました。");
374-
hasAlerted = true;
375-
}
376-
return;
377-
}
348+
useEffect(() => {
349+
let hasAlerted = false;
378350

379-
const imageXY = convertLatLngToImageXY(userLat, userLng);
351+
// デバッグ用固定位置がある場合はそれを使用
352+
if (debugPosition) {
353+
setPosition(debugPosition);
354+
setAccuracy(0);
355+
return;
356+
}
380357

381-
if (!imageXY || !Array.isArray(imageXY) || imageXY.length < 2) {
382-
console.error("Failed to convert coordinates");
383-
if (!hasAlerted) {
384-
alert("座標変換に失敗しました。");
385-
hasAlerted = true;
358+
if (!navigator.geolocation) {
359+
console.error("Geolocation is not supported");
360+
return;
361+
}
362+
363+
const watchId = navigator.geolocation.watchPosition(
364+
(position: GeolocationPosition) => {
365+
const userLat: number = position.coords.latitude;
366+
const userLng: number = position.coords.longitude;
367+
const accuracy = position.coords.accuracy;
368+
369+
// Convert latitude/longitude to image coordinates
370+
if (!convertLatLngToImageXY) {
371+
console.error("Coordinate transformer is not available");
372+
if (!hasAlerted) {
373+
alert("座標変換システムの初期化に失敗しました。");
374+
hasAlerted = true;
375+
}
376+
return;
386377
}
387-
return;
388-
}
389378

390-
const imgY = imageXY[0];
391-
const imgX = imageXY[1];
379+
const imageXY = convertLatLngToImageXY(userLat, userLng);
380+
if (!imageXY) {
381+
console.error("Failed to convert coordinates");
382+
if (!hasAlerted) {
383+
alert("座標変換に失敗しました。");
384+
hasAlerted = true;
385+
}
386+
return;
387+
}
392388

393-
if (typeof imgY !== "number" || typeof imgX !== "number") {
394-
console.error("Invalid coordinate values");
395-
return;
396-
}
389+
const imgY = imageXY[0];
390+
const imgX = imageXY[1];
397391

398-
// Check if coordinates are outside the map bounds
399-
if (imgX < 0 || imgX > imgWidth || imgY < 0 || imgY > imgHeight) {
400-
console.warn("User location is outside the map bounds", {
401-
lat: userLat,
402-
lng: userLng,
403-
imageXY: { x: imgX, y: imgY },
404-
});
405-
if (!hasAlerted) {
406-
alert("現在地がマップの範囲外です。");
407-
hasAlerted = true;
392+
if (typeof imgY !== "number" || typeof imgX !== "number") {
393+
console.error("Invalid coordinate values");
394+
return;
408395
}
409-
return;
410-
}
411396

412-
placeOrUpdateMarker(map, [imgY, imgX], accuracy, () => {
413-
userMarker = null;
414-
userCircle = null;
415-
});
397+
// Check if coordinates are outside the map bounds
398+
if (imgX < 0 || imgX > imgWidth || imgY < 0 || imgY > imgHeight) {
399+
console.warn("User location is outside the map bounds", {
400+
lat: userLat,
401+
lng: userLng,
402+
imageXY: { x: imgX, y: imgY },
403+
});
404+
if (!hasAlerted) {
405+
alert("現在地がマップの範囲外です。");
406+
hasAlerted = true;
407+
}
408+
return;
409+
}
416410

417-
// Show error warning only when inside map bounds and error is large
418-
if (accuracy > ERROR_THRESHOLD_METERS && !hasAlerted) {
419-
alert(
420-
`現在地は正確ではない可能性があります(誤差:約${Math.round(accuracy)}m)`,
411+
// Show error warning only when inside map bounds and error is large
412+
if (accuracy > ERROR_THRESHOLD_METERS && !hasAlerted) {
413+
alert(
414+
`現在地は正確ではない可能性があります(誤差:約${Math.round(accuracy)}m)`,
415+
);
416+
hasAlerted = true;
417+
}
418+
// Log the obtained values
419+
console.log(
420+
"gps:",
421+
{ lat: userLat, lng: userLng, error_m: accuracy },
422+
"imageXY:",
423+
{ y: Number(imgY.toFixed(2)), x: Number(imgX.toFixed(2)) },
421424
);
422-
hasAlerted = true;
423-
}
424-
425-
// Log the obtained values
426-
console.log(
427-
"gps:",
428-
{ lat: userLat, lng: userLng, error_m: accuracy },
429-
"imageXY:",
430-
{ y: Number(imgY.toFixed(2)), x: Number(imgX.toFixed(2)) },
431-
);
432-
},
433-
(error: GeolocationPositionError) => {
434-
console.error("Failed to get location information", error);
435-
// ユーザーが位置情報の共有を拒否した場合(PERMISSION_DENIED)はアラートを表示しない
436-
if (error.code !== error.PERMISSION_DENIED && !hasAlerted) {
437-
alert("位置情報の取得に失敗しました。");
438-
hasAlerted = true;
439-
}
440-
},
441-
{
442-
enableHighAccuracy: true,
443-
maximumAge: 5_000,
444-
timeout: 10_000,
445-
},
446-
);
447-
448-
return {
449-
cleanup: () => {
450-
if (watchId !== null) {
451-
navigator.geolocation.clearWatch(watchId);
452-
}
453-
},
454-
};
455-
}
456-
457-
function placeOrUpdateMarker(
458-
map: L.Map,
459-
latlng: L.LatLngExpression,
460-
accuracy: number,
461-
onClear: () => void,
462-
) {
463-
// Remove existing marker/circle
464-
const existingMarker = (map as any)._userMarker as L.Marker | undefined;
465-
const existingCircle = (map as any)._userCircle as L.Circle | undefined;
466-
if (existingMarker) {
467-
map.removeLayer(existingMarker);
468-
}
469-
if (existingCircle) {
470-
map.removeLayer(existingCircle);
471-
}
425+
},
426+
(err) => {
427+
console.error("Failed to get location information", err);
428+
if (err.code !== err.PERMISSION_DENIED && !hasAlerted) {
429+
alert("位置情報の取得に失敗しました。");
430+
hasAlerted = true;
431+
}
432+
},
433+
{ enableHighAccuracy: true, maximumAge: 5000, timeout: 10000 },
434+
);
472435

473-
// 精度円 (accuracy radius in meters, but since this is a custom CRS map,
474-
// we use a visual radius in pixels)
475-
const radius = Math.max(accuracy, 10);
476-
const circle = L.circle(latlng, {
477-
radius,
478-
color: "#3b82f6",
479-
fillColor: "#3b82f6",
480-
fillOpacity: 0.2,
481-
weight: 2,
482-
}).addTo(map);
483-
484-
// 中心点のマーカー(小さなドット)
485-
const dotIcon = L.divIcon({
486-
className: "user-location-dot",
487-
html: `<div style="
488-
width: 16px;
489-
height: 16px;
490-
background: #3b82f6;
491-
border: 3px solid white;
492-
border-radius: 50%;
493-
box-shadow: 0 0 6px rgba(59,130,246,0.6);
494-
"></div>`,
495-
iconSize: [16, 16],
496-
iconAnchor: [8, 8],
497-
});
436+
return () => navigator.geolocation.clearWatch(watchId);
437+
}, [debugPosition, imgWidth, imgHeight]);
498438

499-
const marker = L.marker(latlng, {
500-
icon: dotIcon,
501-
zIndexOffset: 1000,
502-
})
503-
.addTo(map)
504-
.bindPopup("Current Location")
505-
.openPopup();
506-
507-
// Store references on map for cleanup
508-
(map as any)._userMarker = marker;
509-
(map as any)._userCircle = circle;
439+
return { position, accuracy };
510440
}

0 commit comments

Comments
 (0)