Skip to content

Instantly share code, notes, and snippets.

@larshei
Created September 19, 2023 19:58
Show Gist options
  • Select an option

  • Save larshei/249ebc49b7469c41d8ea4260fe9ad93a to your computer and use it in GitHub Desktop.

Select an option

Save larshei/249ebc49b7469c41d8ea4260fe9ad93a to your computer and use it in GitHub Desktop.
Leaflet Map as Elixir Phoenix LiveView Component

Note

This was just a quick copy/paste of snippets from a project.

I dod not get to test this version yet.

// Add this to your app.js phoenix application
let Hooks = {}
Hooks.Map = {
mounted(){
const markers = {}
const map = L.map('mapid').setView([51.505, -0.09], 14)
const paths = {}
let geojsonLayer = L.geoJSON().addTo(map).setStyle({color: "#6435c9"})
L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
maxZoom: 18,
id: 'mapbox/streets-v11',
tileSize: 512,
zoomOffset: -1,
accessToken: YOUR_MAPBOX_ACCESS_TOKEN
}).addTo(map)
this.handleEvent("update_marker_position", ({reference, lat, lon, center_view}) => {
markers[reference].setLatLng(L.latLng(lat, lon))
if (center_view) {
map.flyTo(L.latLng(lat, lon))
}
})
this.handleEvent("draw_path", ({reference, coordinates, color}) => {
data = {
"type": "LineString",
"coordinates": coordinates
}
geojsonLayer.addData(data)
})
this.handleEvent("view_init", ({reference, lat, lon, zoom_level = 20}) => {
geojsonLayer.remove()
geojsonLayer = L.geoJSON().addTo(map).setStyle({color: "#6435c9"})
map.setView(L.latLng(lat, lon), zoom_level)
})
this.handleEvent("set_zoom_level", ({zoom_level}) => {
map.setZoom(zoom_level)
})
this.handleEvent("add_marker", ({reference, lat, lon}) => {
// lets not add duplicates for the same marker!
if (markers[reference] == null) {
const marker = L.marker(L.latLng(lat, lon))
marker.addTo(map)
markers[reference] = marker
}
})
this.handleEvent("add_marker_with_popup", ({reference, lat, lon, link}) => {
// lets not add duplicates for the same marker!
if (markers[reference] == null) {
const marker = L.marker(L.latLng(lat, lon))
marker.bindPopup(`<a href=\"${link}\">${reference}</a>`)
marker.addTo(map)
markers[reference] = marker
}
})
this.handleEvent("clear", () => {
geojsonLayer.remove()
geojsonLayer = L.geoJSON().addTo(map)
for (const [reference, value] of Object.entries(markers)) {
marker = markers[reference]
marker.remove()
markers.delete(reference)
}
})
this.handleEvent("remove_marker", ({reference}) => {
if (markers[reference] != null) {
marker = markers[reference]
marker.remove()
markers.delete(reference)
}
geojsonLayer.remove()
})
}
}
defmodule Components.LeafletMap do
use Phoenix.Component
attr :class, :string, default: nil
def map(assigns) do
~H"""
<div style="overflow: hidden" phx-update="ignore" id="mapcontainer">
<div class={@class} phx-hook="Map" id="mapid"></div>
</div>
"""
end
# might be off if the maps shape is not close to a square.
def calculate_initial_map_zoom_level(n, e, s, w) do
lat_to_radiant = fn lat ->
sin = :math.sin(lat * :math.pi() / 180)
radX2 = :math.log((1 + sin) / (1 - sin)) / 2
max(min(radX2, :math.pi()), -:math.pi()) / 2
end
lat_difference = abs(lat_to_radiant.(n) - lat_to_radiant.(s))
lon_difference = abs(e - w)
lat_fraction = lat_difference / :math.pi()
lon_fraction = lon_difference / 360
# Ensure we never get 0 in division. 1.0e-5 was chosen arbitrarily after trying different values.
lat_zoom = :math.log(1 / max(lat_fraction, 1.0e-5)) / :math.log(2)
lon_zoom = :math.log(1 / max(lon_fraction, 1.0e-5)) / :math.log(2)
# Slight zoom out for vertical dimension, because our map view is very wide and not square.
min(lat_zoom - 0.5, lon_zoom)
|> min(20) # Lets not zoom in to infinity
end
def liveview_setup_map(socket, opts \\ []) do
socket
|> assign(selected_address: address)
|> Phoenix.LiveView.push_event("view_init", %{
reference: opts[:reference],
lat: opts[:latitude],
lon: opts[:longitude],
zoom_level: opts[:zoom_level] || 15
})
|> Phoenix.LiveView.push_event("add_marker", %{
reference: opts[:reference],
lat: opts[:latitude],
lon: opts[:longitude]
})
|> Phoenix.LiveView.push_event("update_marker_position", %{
reference: opts[:reference],
lat: address[:latitude],
lon: address[:longitude],
center_view: true
})
end
end
defmodule MyAppWeb.MapLive do
use MyAppWeb, :live_view
@impl true
def mount(%{"id" => id}, _session, socket) do
opts = [latitude: 51.123456, longitude: 7.123456, reference: "main"]
LeafletMap.liveview_set_map_to_address(socket, opts)
end
@impl true
def render(assigns) do
~H"""
<div class="h-screen bg-gray-100">
<div class="flex h-screen justify-center items-center rounded-md shadow-lg">
<LeafletMap.map class="h-80" />
</div>
</div>
"""
end
end
@larshei
Copy link
Author

larshei commented Sep 20, 2023

@nomtrosk right, sorry for rushing it out like this, but glad you got it to work in the end!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment