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 © <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 |
Great approach to get leaflet working in Elixir!
I spent some time getting this to work: The biggest time consumer was to discover that the
flex
class in line 15 of map_live would render the map gray. Anyways, here is what else i changed to make it runliveview_setup_map
(looked like some left overs)liveview_setup_map
instead ofliveview_set_map_to_address
inmap_live.ex
<head>
inroot.html.heex
from hosted source https://leafletjs.com/download.htmlalias Components.LeafletMap, as: LeafletMap
to map_live.exapp.js
by altering this line: