Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User geolocation in Google Maps #784

Merged
merged 22 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions extension.json
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@
"maps-searchmarkers-text",
"maps-fullscreen-button",
"maps-fullscreen-button-tooltip",
"maps-mylocation-button-tooltip",
"maps-kml-parsing-failed"
],
"targets": [ "desktop", "mobile" ]
Expand Down Expand Up @@ -307,6 +308,17 @@
"targets": [ "desktop", "mobile" ]
},

"ext.maps.mylocation": {
"dependencies": [
"ext.maps.googlemaps3",
"ext.sm.common"
],
"scripts": [
"GoogleMaps/mylocation.js"
],
"targets": [ "desktop", "mobile" ]
},

"ext.maps.wikitext.editor": {
"dependencies": [
"jquery.ui",
Expand Down
2 changes: 2 additions & 0 deletions i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@
"maps-displaymap-par-geojson": "URL of a file or name of the page containing GeoJSON data",
"maps-fullscreen-button": "Toggle fullscreen",
"maps-fullscreen-button-tooltip": "View the map as fullscreen or embedded.",
"maps-mylocation-button-tooltip": "Show my location on the map.",
"validation-error-invalid-location": "Parameter \"$1\" must be a valid location.",
"validation-error-invalid-locations": "Parameter \"$1\" must be one or more valid locations.",
"validation-error-invalid-width": "Parameter \"$1\" must be a valid width.",
Expand Down Expand Up @@ -125,6 +126,7 @@
"maps-par-height": "Allows setting the height of the map. By default pixels will be assumed as unit, but you can explicitly specify one of these units: px, ex, em.",
"maps-par-centre": "The location on which the map should be centered",
"maps-par-enable-fullscreen": "Enable fullscreen button",
"maps-par-enable-mylocation": "Enable the geolocation button",
"maps-par-kml": "KML files to load onto the map.",
"maps-par-markercluster": "Allows merging of multiple nearby markers into one marker",
"maps-googlemaps3-incompatbrowser": "Your browser is not compatible with Google Maps v3.",
Expand Down
2 changes: 1 addition & 1 deletion resources/GoogleMaps/googlemaps3ajax.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

(function( $, sm ) {
var ajaxRequest = null;
var mapEvents = ['dragend', 'zoom_changed'];
var mapEvents = ['dragend', 'bounds_changed'];

$( document ).ready( function() {
// todo: find a way to remove setTimeout.
Expand Down
Binary file added resources/GoogleMaps/img/mylocation-sprite-2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions resources/GoogleMaps/jquery.googlemap.js
Original file line number Diff line number Diff line change
Expand Up @@ -731,6 +731,11 @@
if(options.fullscreen){
this.map.controls[google.maps.ControlPosition.TOP_RIGHT].push(new FullscreenControl(this.map));
}

// - My Location
if(options.mylocation){
this.map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(new window.MyLocationControl(this.map));
}
};

this.setup = function () {
Expand Down
172 changes: 172 additions & 0 deletions resources/GoogleMaps/mylocation.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
// Control for toggling the user location function
function MyLocationControl( map ) {
var controlDiv = document.createElement('div');
controlDiv.style.padding = '10px 10px 0px 10px';
controlDiv.index = 1;

var controlUI = document.createElement('div');
controlUI.style.padding = '6px 6px';
controlUI.style.backgroundColor = 'white';
controlUI.style.borderStyle = 'solid';
controlUI.style.borderColor = 'rgba(0, 0, 0, 0.14902)';
controlUI.style.borderWidth = '1px';
controlUI.style.borderRadius = '2px';
controlUI.style.cursor = 'pointer';
controlUI.style.textAlign = 'center';
controlUI.style.boxShadow = 'rgba(0, 0, 0, 0.298039) 0px 1px 4px -1px';
controlUI.style.backgroundClip = 'padding-box';
controlUI.title = mw.msg('maps-mylocation-button-tooltip');
controlDiv.appendChild(controlUI);

var controlText = document.createElement('div');
controlText.style.backgroundPosition = '0 0';
controlText.style.backgroundImage = 'url(' + mw.config.get( 'egMapsScriptPath' ) + '/resources/GoogleMaps/img/mylocation-sprite-2x.png)';
controlText.style.backgroundSize = '180px 18px';
controlText.style.display = 'block';
controlText.style.height = '18px';
controlText.style.left = '6px';
controlText.style.margin = '0';
controlText.style.padding = '0';
controlText.style.width = '18px';
controlUI.appendChild(controlText);

// Handle toggle button click
google.maps.event.addDomListener( controlUI, 'click', function() {
let mapDiv = $( map.getDiv() );

if ( mapDiv.data( 'followMyLocation' ) != null ) {
mapDiv.removeData( 'followMyLocation' );
controlText.style.backgroundPosition = '0 0';
deactivateMyLocation( map );
} else {
mapDiv.data( 'followMyLocation', 'locked' );
controlText.style.backgroundPosition = '-144px 0';
activateMyLocation( map );
}
} );

// Handle dragged map
google.maps.event.addDomListener( map, 'dragend', function() {
let mapDiv = $( map.getDiv() );

// Continue tracking location, without centering on user
if ( mapDiv.data( 'followMyLocation' ) != null ) {
mapDiv.data( 'followMyLocation', 'passive' );
}
} );

return controlDiv;
}
window.MyLocationControl = MyLocationControl;

function handleLocationError( browserHasGeolocation, pos ) {
console.log( browserHasGeolocation ?
'Error: The Geolocation service failed.' :
'Error: Your browser doesn\'t support geolocation.' );
}

function drawMyLocation( position, map ) {
let pos = {
lat: position.coords.latitude,
lng: position.coords.longitude
};
let radius = position.coords.accuracy * 0.5;

let mapDiv = $( map.getDiv() );

if ( typeof mapDiv.data( 'myLocationMarker' ) === 'undefined' ) {
// Add a circle to visualize geolocation accuracy
let myLocationCircle = new google.maps.Circle( {
strokeWeight: 0,
fillColor: "#5383EC",
fillOpacity: 0.2,
map,
center: pos,
radius: radius,
} );

// Add a marker at the user's location
const svgMarker = {
path: "M 11, 11 m 10, 0 a 10,10 0 1,0 -20,0 a 10,10 0 1,0 20,0",
fillColor: "#5383EC",
fillOpacity: 1,
strokeWeight: 2,
strokeColor: "white",
anchor: new google.maps.Point( 11, 11 ),
scale: 0.75,
};

let myLocationMarker = new google.maps.Marker( {
position: pos,
icon: svgMarker,
map: map,
} );

// Zoom into user's location
map.setZoom( 16 );

// Store for later access
mapDiv.data( 'myLocationMarker', myLocationMarker );
mapDiv.data( 'myLocationCircle', myLocationCircle );
} else {
// Update position and radius
mapDiv.data( 'myLocationMarker' ).setPosition( pos );
mapDiv.data( 'myLocationCircle' ).setCenter( pos );
mapDiv.data( 'myLocationCircle' ).setRadius( radius );
}

if ( mapDiv.data( 'followMyLocation' ) === 'locked' ) {
// Center the map on the user's location
map.setCenter( pos );
}
}

function activateMyLocation( map ) {
mapDiv = $( map.getDiv() );

// Check if geolocation is supported
if ( navigator.geolocation ) {
let myLocationWatchId = navigator.geolocation.watchPosition(
function( position ) {
drawMyLocation( position, map );
},
// Error handling
function() {
handleLocationError( true, map.getCenter() );
},
// Geolocation options
{
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0,
}
);
mapDiv.data( 'myLocationWatchId', myLocationWatchId );
} else {
// Browser doesn't support geolocation
handleLocationError( false, map.getCenter() );
}
}

function deactivateMyLocation( map ) {
mapDiv = $( map.getDiv() );

// Check if geolocation is supported
if ( navigator.geolocation ) {
// Stop tracking location
navigator.geolocation.clearWatch( mapDiv.data( 'myLocationWatchId' ) );
mapDiv.removeData( 'myLocationWatchId' );
}

// Remove marker from the map
if ( typeof mapDiv.data( 'myLocationMarker' ) !== 'undefined' ) {
mapDiv.data( 'myLocationMarker' ).setMap( null );
mapDiv.removeData( 'myLocationMarker' );
}

// Remove circle from the map
if ( typeof mapDiv.data( 'myLocationCircle' ) !== 'undefined' ) {
mapDiv.data( 'myLocationCircle' ).setMap( null );
mapDiv.removeData( 'myLocationCircle' );
}
}
9 changes: 8 additions & 1 deletion src/GoogleMapsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,13 @@ function( string $fileName ) {
'message' => 'maps-par-enable-fullscreen',
];

$params['mylocation'] = [
'aliases' => [ 'enablemylocation' ],
'type' => 'boolean',
'default' => false,
'message' => 'maps-par-enable-mylocation',
];

$params['scrollwheelzoom'] = [
'aliases' => [ 'scrollzoom' ],
'type' => 'boolean',
Expand All @@ -273,7 +280,7 @@ public function newMapId(): string {
}

public function getResourceModules( array $params ): array {
return [ 'ext.maps.googlemaps3', 'ext.maps.googlemaps3ajax' ];
return [ 'ext.maps.googlemaps3', 'ext.maps.googlemaps3ajax', 'ext.maps.mylocation' ];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you always load the new js file anyway, you could put it in one of the existing modules.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I'm not very familiar how script loading works in MediaWiki, but I've added it to the ext.maps.googlemaps3 module now.

}

public static function getApiScript( $langCode, array $urlArgs = [] ) {
Expand Down