Last active
December 15, 2022 09:07
-
-
Save amkisko/2f1cc0a363ccb53fa7a3045575035772 to your computer and use it in GitHub Desktop.
leaflet osm address search and reverse coordinates lookup with stimulus for Rails
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<div data-controller="address-form"> | |
<%= simple_form_for(address, url: address_path(address)) do |f| %> | |
<%= f.text_field :address, "data-address-form-target" => "address" %> | |
<%= f.hidden_field :address_lon, "data-address-form-target" => "addressLon" %> | |
<%= f.hidden_field :address_lat, "data-address-form-target" => "addressLat" %> | |
<div data-address-form-target="addressMapContainer" style="width: 500px; height: 500px; margin-left: -3.6rem;"></div> | |
<%= f.button :submit %> | |
<% end %> | |
</div> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Controller } from "@hotwired/stimulus"; | |
import { | |
Address, | |
LocationEditor, | |
} from "../../../javascript/lib/location_editor"; | |
export default class extends Controller { | |
static targets = [ | |
"addressLon", | |
"addressLat", | |
"address", | |
"addressMapContainer", | |
]; | |
declare readonly addressLonTarget: HTMLInputElement; | |
declare readonly addressLatTarget: HTMLInputElement; | |
declare readonly addressTarget: HTMLInputElement; | |
declare readonly addressMapContainerTarget: HTMLDivElement; | |
locationEditor: LocationEditor | undefined; | |
connect() { | |
this.locationEditor = new LocationEditor( | |
this.addressLonTarget, | |
this.addressLatTarget, | |
this.addressMapContainerTarget, | |
(address) => this.setAddress(address) | |
); | |
this.locationEditor.getUserPosition(); | |
} | |
setAddress(address: Address | undefined) { | |
if (address) { | |
this.addressTarget.value = this.locationEditor.formatAddress(address); | |
} else { | |
this.addressTarget.value = ""; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import "leaflet/dist/leaflet.css"; | |
import "leaflet-control-geocoder/dist/Control.Geocoder.css"; | |
import * as L from "leaflet"; | |
import * as LG from "leaflet-control-geocoder"; | |
import { MarkGeocodeEvent } from "leaflet-control-geocoder/dist/control"; | |
export type Address = LG.geocoders.NominatimAddress; | |
export type SetAddressFunction = (address: Address | undefined) => void; | |
export class LocationEditor { | |
DEFAULT_LAT = 60.170718; | |
DEFAULT_LON = 24.941469; | |
geocoder = LG.geocoders.nominatim({ | |
reverseQueryParams: { | |
addressdetails: true, | |
zoom: 18, | |
"accept-language": "en-US", | |
}, | |
geocodingQueryParams: { | |
addressdetails: true, | |
zoom: 18, | |
"accept-language": "en-US", | |
}, | |
}); | |
map: L.Map | undefined; | |
userLocation: GeolocationCoordinates | undefined; | |
marker: L.Marker | undefined; | |
lonInput: HTMLInputElement; | |
latInput: HTMLInputElement; | |
setAddress: SetAddressFunction; | |
constructor( | |
lonInput: HTMLInputElement, | |
latInput: HTMLInputElement, | |
mapContainer: HTMLElement, | |
setAddress: SetAddressFunction | |
) { | |
this.lonInput = lonInput; | |
this.latInput = latInput; | |
this.setAddress = setAddress; | |
this.init(mapContainer); | |
} | |
getUserPosition() { | |
if (navigator.geolocation) { | |
navigator.geolocation.getCurrentPosition((pos) => { | |
this.userLocation = pos.coords; | |
}); | |
} | |
} | |
init(element: HTMLElement) { | |
const lat = this.latInput.value ? +this.latInput.value : this.DEFAULT_LAT; | |
const lon = this.lonInput.value ? +this.lonInput.value : this.DEFAULT_LON; | |
this.map = L.map(element).setView([lat, lon], 13); | |
L.tileLayer("//{s}.tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(this.map); | |
L.Icon.Default.imagePath = "/images/leaflet/"; | |
const geocoderControl = LG.geocoder({ | |
geocoder: this.geocoder, | |
placeholder: "Search", | |
defaultMarkGeocode: false, | |
}); | |
geocoderControl.addTo(this.map); | |
geocoderControl.on("markgeocode", (event) => | |
this.onMapSearchResultClick(event) | |
); | |
this.map.on("click", (event) => this.onMapClick(event)); | |
if (this.latInput.value && this.lonInput.value) { | |
const coords = new L.LatLng(+this.latInput.value, +this.lonInput.value); | |
this.updateCoordinates(coords); | |
} | |
} | |
onMapSearchResultClick(event: MarkGeocodeEvent) { | |
this.updateCoordinates(event.geocode.center); | |
this.updateAddress(event.geocode.properties.address); | |
} | |
onMapClick(event: L.LeafletMouseEvent) { | |
this.updateCoordinates(event.latlng); | |
this.setAddress(undefined); | |
this.geocoder.reverse(event.latlng, 100000000, (results) => { | |
if (!results) return; | |
this.updateAddress(results[0].properties.address); | |
}); | |
} | |
updateCoordinates(coords: L.LatLng) { | |
if (this.marker) this.marker.remove(); | |
this.marker = L.marker(coords).addTo(this.map as L.Map); | |
this.latInput.value = coords.lat.toString(); | |
this.lonInput.value = coords.lng.toString(); | |
} | |
updateAddress(address: Address) { | |
this.setAddress(address); | |
} | |
formatAddress(address: Address): string { | |
return `${address.postcode || ""}, ${ | |
address.city || address.city_district || "" | |
}, ${address.road || ""}, ${address.house_number || ""}`; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment