Skip to content

Commit 3f3554e

Browse files
committed
react-leafletに移行
1 parent be93c86 commit 3f3554e

5 files changed

Lines changed: 591 additions & 619 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: 95 additions & 163 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];
@@ -334,177 +335,108 @@ const convertLatLngToImageXY = createPolynomialTransformer(refPoints);
334335
export interface GeolocationOptions {
335336
/** デバッグ用: 現在地を固定座標(画像ピクセル)で指定する */
336337
debugPosition?: [number, number];
338+
imgWidth: number;
339+
imgHeight: number;
337340
}
338341

339-
export function setupGeolocation(
340-
map: L.Map,
341-
imgWidth: number,
342-
imgHeight: number,
343-
options?: GeolocationOptions,
344-
) {
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-
}
361-
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-
}
342+
export function useGeolocation({
343+
debugPosition,
344+
imgWidth,
345+
imgHeight,
346+
}: GeolocationOptions) {
347+
const [position, setPosition] = useState<[number, number] | null>(null);
348+
const [accuracy, setAccuracy] = useState<number>(0);
349+
350+
useEffect(() => {
351+
let hasAlerted = false;
352+
353+
// デバッグ用固定位置がある場合はそれを使用
354+
if (debugPosition) {
355+
setPosition(debugPosition);
356+
setAccuracy(0);
357+
return;
358+
}
378359

379-
const imageXY = convertLatLngToImageXY(userLat, userLng);
360+
if (!navigator.geolocation) {
361+
console.error("Geolocation is not supported");
362+
return;
363+
}
380364

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

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

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

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;
394+
if (typeof imgY !== "number" || typeof imgX !== "number") {
395+
console.error("Invalid coordinate values");
396+
return;
408397
}
409-
return;
410-
}
411398

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

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)`,
413+
// Show error warning only when inside map bounds and error is large
414+
if (accuracy > ERROR_THRESHOLD_METERS && !hasAlerted) {
415+
alert(
416+
`現在地は正確ではない可能性があります(誤差:約${Math.round(accuracy)}m)`,
417+
);
418+
hasAlerted = true;
419+
}
420+
// Log the obtained values
421+
console.log(
422+
"gps:",
423+
{ lat: userLat, lng: userLng, error_m: accuracy },
424+
"imageXY:",
425+
{ y: Number(imgY.toFixed(2)), x: Number(imgX.toFixed(2)) },
421426
);
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-
}
427+
},
428+
(err) => {
429+
console.error("Failed to get location information", err);
430+
if (err.code !== err.PERMISSION_DENIED && !hasAlerted) {
431+
alert("位置情報の取得に失敗しました。");
432+
hasAlerted = true;
433+
}
434+
},
435+
{ enableHighAccuracy: true, maximumAge: 5000, timeout: 10000 },
436+
);
472437

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-
});
438+
return () => navigator.geolocation.clearWatch(watchId);
439+
}, [debugPosition, imgWidth, imgHeight]);
498440

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;
441+
return { position, accuracy };
510442
}

0 commit comments

Comments
 (0)