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

Update map UI #141

Merged
merged 9 commits into from
Nov 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 17 additions & 8 deletions app/assets/stylesheets/application.tailwind.css.scss
Original file line number Diff line number Diff line change
Expand Up @@ -56,17 +56,22 @@
border-radius: 0 0 10px 10px;
}

.map {
@apply w-full h-80 mt-2;
}
#map-wrapper {
@apply flex mt-2;

.map {
@apply w-full h-80;
}

.daqi-scale {
@apply flex flex-row;
.daqi-scale {
@apply flex flex-col-reverse;

div {
@apply flex py-1 justify-center items-center;
div {
@apply flex py-1 justify-center items-center;

width: 10%;
width: 2rem;
height: 10%;
}
}
}

Expand Down Expand Up @@ -205,3 +210,7 @@

background-color: #eee;
}

select {
@apply inline-flex flex-row w-full gap-x-1.5 rounded-md bg-white px-3 py-2 text-base font-semibold text-gray-500 shadow-sm ring-1 ring-inset ring-gray-300 hover:bg-gray-50;
}
50 changes: 41 additions & 9 deletions app/assets/stylesheets/leaflet.scss
Original file line number Diff line number Diff line change
@@ -1,19 +1,45 @@
/* Airtext specific styles */

.leaflet-marker-icon.zone-label {
color: black;
font-size: 12px;
width: auto !important;
height: auto !important;
background-color: white;
padding: 0 3px;
text-align: center;
border-radius: 3px;
opacity: 1;

&.zone-label-level-1 {
font-weight: bold;
}

.wrapper {
display: flex;
transform: translate(-50%, -50%);
color: black;
font-size: 12px;
background-color: white;
text-align: center;
border-radius: 3px;
}

.zone-name {
padding: 0 6px;
}

.daqi-indicator {
padding: 0 6px;
border-right: 1px solid #ccc;
display: flex;
align-items: center;

/*
&::before {
content: "";
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 4px;
background-color: red;
}
*/
}
}

.fullscreen-icon {
Expand All @@ -27,10 +53,16 @@
}
}

.leaflet-control-attribution {
font-size: 50%;
}

.leaflet-bottom.leaflet-left {
bottom: 10px !important;
}

.leaflet-container {
a {
left: 2px !important;

img {
width: 50px; /* Make the Maptiler logo smaller to fit on mobile */
}
Expand Down
18 changes: 17 additions & 1 deletion app/controllers/forecasts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ def show
@day = @date ? day_from_date(@date) : day
@pollutant = pollutant

@forecasts = CercForecastService.latest_forecasts_for(@zone).data
@forecasts = CercForecastService.latest_forecasts(@zone).data
@day_forecast = forecast_for_day(@day, @forecasts)
@share_message = @day_forecast.share_message

Expand All @@ -27,6 +27,22 @@ def show
end
end

def pollutant_forecasts
@date = date
@day = @date ? day_from_date(@date) : day
@pollutant = pollutant

latest_forecasts = CercForecastService.latest_forecasts

pollutant_forecasts = latest_forecasts.each_with_object({}) do |zone_forecasts, hash|
zone_name = zone_forecasts.data.first.zone[:name]
forecast = forecast_for_day(@day, zone_forecasts.pollutant_forecasts(@pollutant))
hash[zone_name] = forecast
end

render json: pollutant_forecasts
end

private

def day_from_date(date)
Expand Down
82 changes: 42 additions & 40 deletions app/javascript/controllers/map_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export default class MapController extends Controller {
zoomControl: false,
fullscreenControl: true,
fullscreenControlOptions: {
position: "bottomright",
position: "bottomleft",
},
});

Expand Down Expand Up @@ -127,7 +127,7 @@ export default class MapController extends Controller {
addZoomControl() {
L.control
.zoom({
position: "bottomright",
position: "bottomleft",
})
.addTo(this.map);
}
Expand Down Expand Up @@ -165,7 +165,7 @@ export default class MapController extends Controller {

addGeolocationControl() {
new LocateControl({
position: "topright",
position: "topleft",
}).addTo(this.map);
}

Expand Down Expand Up @@ -207,43 +207,24 @@ export default class MapController extends Controller {
}

addZonesLayer() {
const { zoneBoundaryLayers, zoneLabelMarkers } = this.zones();
const zoneBoundaryLayers = this.zoneBoundariesLayers();
zoneBoundaryLayers.forEach((layer) => layer.addTo(this.map));
this.updateZoneBoundaryLayerStyles(zoneBoundaryLayers);

this.map.on("zoomend", () => {
this.updateZoneBoundaryLayerStyles(zoneBoundaryLayers);
});

this.addZoneLabelMarkers(zoneLabelMarkers);
}

zones() {
zoneBoundariesLayers() {
const zoneBoundaryLayers = [];
const zoneLabelMarkers = [];

for (const [, zone] of Object.entries(zones)) {
const layer = L.geoJSON(zone, { pane: "zones" });
zoneBoundaryLayers.push(layer);

const bounds = layer.getBounds();
const center = bounds.getCenter();

const label = L.marker(center, {
icon: L.divIcon({
className:
"zone-label " + `zone-label-level-${zone.properties.level}`,
html: zone.properties.name,
}),
});

zoneLabelMarkers.push({
marker: label,
londonBorough: zone.properties.londonBorough,
});
}

return { zoneBoundaryLayers, zoneLabelMarkers };
return zoneBoundaryLayers;
}

updateZoneBoundaryLayerStyles(zoneBoundaryLayers) {
Expand All @@ -258,27 +239,37 @@ export default class MapController extends Controller {
});
}

addZoneLabelMarkers(zoneLabelMarkers) {
async updateZoneLabels() {
const pollutant_forecasts = await this.getForecastData();

// Remove existing zone labels
this.layers.zoneLabels?.forEach((marker) => this.map.removeLayer(marker));
this.layers.zoneLabels = [];

// Only show the zone labels when the map is zoomed in enough
zoneLabelMarkers.forEach(({ marker, londonBorough }) => {
const startZoom = 9 + (londonBorough ? 2 : 0); // Only show London borough labels at higher zoom
for (const [, zone] of Object.entries(zones)) {
const startZoom = 9 + (zone.properties.londonBorough ? 2 : 0); // Only show London borough labels at higher zoom
const endZoom = 20;

// Add the marker to the map initially if within the zoom range
// Add the marker to the map if within the zoom range
if (this.map.getZoom() >= startZoom && this.map.getZoom() <= endZoom) {
const center = {
lat: zone.properties.center[1],
lng: zone.properties.center[0],
};
const pollutant_value = pollutant_forecasts[zone.properties.name];

const marker = L.marker(center, {
icon: L.divIcon({
className:
"zone-label " + `zone-label-level-${zone.properties.level}`,
html: `<div class="wrapper"><span class="daqi-indicator">${pollutant_value}</span><span class="zone-name">${zone.properties.name}</span></div>`,
}),
});
marker.addTo(this.map);
this.layers.zoneLabels.push(marker);
}

// Attach the zoomend event to control visibility
this.map.on("zoomend", () => {
const currentZoom = this.map.getZoom();
if (currentZoom >= startZoom && currentZoom <= endZoom) {
this.map.addLayer(marker);
} else {
this.map.removeLayer(marker);
}
});
});
}
}

findZones(coordinatesLngLat) {
Expand All @@ -303,10 +294,21 @@ export default class MapController extends Controller {
updateMap() {
this.updateSettings();
this.updatePollutionLayer();
this.updateZoneLabels();
}

updatePollutionLayer() {
this.map.removeLayer(this.layers.pollution);
this.addPollutionLayer();
}

async getForecastData() {
const pollutant = this.pollutantSelectorTarget.value;
const date = this.daySelectorTarget.value;

const response = await fetch(
`/pollutant_forecasts?pollutant=${pollutant}&date=${date}`
);
return response.json();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
"type": "Feature",
"properties": {
"name": "Cambridge",
"level": 1
"level": 1,
"center": [0.13, 52.2]
},
"geometry": {
"type": "Polygon",
Expand Down
Loading