1+ import { useEffect , useState } from "react" ;
12import 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
165166function 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
265266function 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);
334335export 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