Skip to content

Commit

Permalink
Merge pull request #184 from benoitdemaegdt/auto-distance
Browse files Browse the repository at this point in the history
Auto distance
  • Loading branch information
benoitdemaegdt authored Nov 2, 2023
2 parents 62a0f1e + a3695f4 commit a409a8e
Show file tree
Hide file tree
Showing 20 changed files with 162 additions and 419 deletions.
6 changes: 5 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
{
"extends": ["@nuxtjs/eslint-config-typescript"]
"extends": ["@nuxtjs/eslint-config-typescript"],
"rules": {
"semi": ["error", "always"],
"space-before-function-paren": ["error", "never"]
}
}
2 changes: 1 addition & 1 deletion .github/scripts/check_data_health.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function checkGeoJsonDataHealth() {
allLineStrings.push(feature);
// 2 - check if all properties are present
const properties = feature.properties || {};
const requiredKeys = ['line', 'color', 'name', 'distance', 'status'];
const requiredKeys = ['line', 'color', 'name', 'status'];
for (const key of requiredKeys) {
if (!properties.hasOwnProperty(key)) {
console.error(`Missing key '${key}' in LineString properties of file: ${filePath}`);
Expand Down
76 changes: 38 additions & 38 deletions components/Map.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@
</template>

<script setup>
import maplibregl from 'maplibre-gl'
import 'maplibre-gl/dist/maplibre-gl.css'
import style from '@/assets/style.json'
import LegendControl from '@/maplibre/LegendControl'
import FullscreenControl from '@/maplibre/FullscreenControl'
import ShrinkControl from '@/maplibre/ShrinkControl'
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import style from '@/assets/style.json';
import LegendControl from '@/maplibre/LegendControl';
import FullscreenControl from '@/maplibre/FullscreenControl';
import ShrinkControl from '@/maplibre/ShrinkControl';
const { features, options } = defineProps({
features: { type: Array, required: true },
Expand All @@ -26,9 +26,9 @@ const { features, options } = defineProps({
onShrinkControlClick: () => {}
})
}
})
});
const legendModalComponent = ref(null)
const legendModalComponent = ref(null);
const {
plotDoneSections,
Expand All @@ -40,9 +40,9 @@ const {
plotPostponedSections,
plotPois,
fitBounds
} = useMap()
} = useMap();
const { getTooltipHtml, getTooltipPoi } = useTooltip()
const { getTooltipHtml, getTooltipPoi } = useTooltip();
onMounted(() => {
const map = new maplibregl.Map({
Expand All @@ -52,64 +52,64 @@ onMounted(() => {
zoom: 12,
attributionControl: false
})
map.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-left')
map.addControl(new maplibregl.AttributionControl({ compact: false }), 'bottom-left')
map.addControl(new maplibregl.NavigationControl({ showCompass: false }), 'top-left');
map.addControl(new maplibregl.AttributionControl({ compact: false }), 'bottom-left');
if (options.fullscreen) {
const fullscreenControl = new FullscreenControl({
onClick: () => options.onFullscreenControlClick()
})
map.addControl(fullscreenControl, 'top-right')
});
map.addControl(fullscreenControl, 'top-right');
}
if (options.shrink) {
const shrinkControl = new ShrinkControl({
onClick: () => options.onShrinkControlClick()
})
map.addControl(shrinkControl, 'top-right')
});
map.addControl(shrinkControl, 'top-right');
}
const legendControl = new LegendControl({
onClick: () => legendModalComponent.value.openModal()
})
map.addControl(legendControl, 'top-right')
});
map.addControl(legendControl, 'top-right');
map.on('load', () => {
plotDoneSections({ map, features })
plotPlannedSections({ map, features })
plotVarianteSections({ map, features })
plotVariantePostponedSections({ map, features })
plotWipSections({ map, features })
plotUnknownSections({ map, features })
plotPostponedSections({ map, features })
plotPois({ map, features })
fitBounds({ map, features })
})
plotDoneSections({ map, features });
plotPlannedSections({ map, features });
plotVarianteSections({ map, features });
plotVariantePostponedSections({ map, features });
plotWipSections({ map, features });
plotUnknownSections({ map, features });
plotPostponedSections({ map, features });
plotPois({ map, features });
fitBounds({ map, features });
});
// must do this to avoid multiple popups
map.on('click', (e) => {
// console.log('e.lngLat >>', e.lngLat)
const features = map
.queryRenderedFeatures(e.point)
.filter(({ layer }) => layer.source !== 'openmaptiles')
.filter(({ layer }) => layer.source !== 'openmaptiles');
if (features.length === 0) {
return
return;
}
const isPoiLayerClicked = features.some(({ layer }) => layer.id === 'pois')
const isPoiLayerClicked = features.some(({ layer }) => layer.id === 'pois');
if (isPoiLayerClicked) {
const feature = features.find(({ layer }) => layer.id === 'pois')
const feature = features.find(({ layer }) => layer.id === 'pois');
new maplibregl.Popup({ closeButton: false, closeOnClick: true })
.setLngLat(e.lngLat)
.setHTML(getTooltipPoi(feature.properties))
.addTo(map)
.addTo(map);
} else {
new maplibregl.Popup({ closeButton: false, closeOnClick: true })
.setLngLat(e.lngLat)
.setHTML(getTooltipHtml(features[0].properties))
.addTo(map)
.setHTML(getTooltipHtml(features[0]))
.addTo(map);
}
})
})
});
});
</script>

<style>
Expand Down
14 changes: 8 additions & 6 deletions components/ProgressBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@
</template>

<script setup>
const { getAllUniqSections, getDistance } = useStats()
const { getAllUniqLineStrings, getDistance } = useStats();
const { voies } = defineProps({
voies: { type: Array, required: true }
})
});
const allSections = getAllUniqSections(voies)
const totalDistance = getDistance({ allSections, status: ['done', 'wip', 'planned', 'postponed', 'unknown', 'variante', 'variante-postponed'] })
const doneDistance = getDistance({ allSections, status: ['done'] })
const features = getAllUniqLineStrings(voies);
const doneFeatures = features.filter(feature => feature.properties.status === 'done');
const percent = Math.round(doneDistance / totalDistance * 100)
const totalDistance = getDistance({ features });
const doneDistance = getDistance({ features: doneFeatures });
const percent = Math.round(doneDistance / totalDistance * 100);
</script>
46 changes: 23 additions & 23 deletions components/Stats.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,36 @@
</template>

<script setup>
const { getAllUniqSections, getDistance } = useStats()
const { getAllUniqLineStrings, getDistance } = useStats();
const { voies } = defineProps({
voies: { type: Array, required: true }
})
});
const allSections = getAllUniqSections(voies)
const features = getAllUniqLineStrings(voies);
const doneFeatures = features.filter(feature => feature.properties.status === 'done');
const wipFeatures = features.filter(feature => feature.properties.status === 'wip');
const plannedFeatures = features.filter(feature => ['planned', 'unknown', 'variante'].includes(feature.properties.status));
const postponedFeatures = features.filter(feature => ['postponed', 'variante-postponed'].includes(feature.properties.status));
const total = getDistance({ allSections, status: ['done', 'wip', 'planned', 'postponed', 'unknown', 'variante', 'variante-postponed'] })
const done = {
distance: getDistance({ allSections, status: ['done'] }),
percent: Math.round(getDistance({ allSections, status: ['done'] }) / total * 100)
}
const wip = {
distance: getDistance({ allSections, status: ['wip'] }),
percent: Math.round(getDistance({ allSections, status: ['wip'] }) / total * 100)
}
const planned = {
distance: getDistance({ allSections, status: ['planned', 'unknown', 'variante'] }),
percent: Math.round(getDistance({ allSections, status: ['planned', 'unknown', 'variante'] }) / total * 100)
const totalDistance = getDistance({ features });
const doneDistance = getDistance({ features: doneFeatures });
const wipDistance = getDistance({ features: wipFeatures });
const plannedDistance = getDistance({ features: plannedFeatures });
const postponedDistance = getDistance({ features: postponedFeatures });
function getPercent(distance) {
return Math.round(distance / totalDistance * 100);
}
const postponed = {
distance: getDistance({ allSections, status: ['postponed', 'variante-postponed'] }),
percent: Math.round(getDistance({ allSections, status: ['postponed', 'variante-postponed'] }) / total * 100)
function getDistanceInKm(distance) {
return Math.round(distance / 1000);
}
const stats = [
{ name: 'Réalisés', distance: `${done.distance} km`, percent: `${done.percent}%` },
{ name: 'En travaux', distance: `${wip.distance} km`, percent: `${wip.percent}%` },
{ name: 'Prévus', distance: `${planned.distance} km`, percent: `${planned.percent}%` },
{ name: 'Reportés', distance: `${postponed.distance} km`, percent: `${postponed.percent}%` }
]
{ name: 'Réalisés', distance: `${getDistanceInKm(doneDistance)} km`, percent: `${getPercent(doneDistance)}%` },
{ name: 'En travaux', distance: `${getDistanceInKm(wipDistance)} km`, percent: `${getPercent(wipDistance)}%` },
{ name: 'Prévus', distance: `${getDistanceInKm(plannedDistance)} km`, percent: `${getPercent(plannedDistance)}%` },
{ name: 'Reportés', distance: `${getDistanceInKm(postponedDistance)} km`, percent: `${getPercent(postponedDistance)}%` }
];
</script>
25 changes: 12 additions & 13 deletions components/content/Overview.vue
Original file line number Diff line number Diff line change
Expand Up @@ -45,28 +45,27 @@
</template>

<script setup>
const { path } = useRoute()
const { path } = useRoute();
const { getDistance } = useStats();
const { voie } = defineProps({ voie: Object })
const { voie } = defineProps({ voie: Object });
const mapOptions = {
fullscreen: true,
onFullscreenControlClick: () => {
const route = useRoute()
return navigateTo({ path: `${route.params._slug}/carte` })
const route = useRoute();
return navigateTo({ path: `${route.params._slug}/carte` });
}
}
};
const { data: geojson } = await useAsyncData(`geojson-${path}`, () => {
return queryContent('voies-lyonnaises')
.where({ _type: 'json', _path: voie._path })
.findOne()
})
.findOne();
});
const features = geojson.value.features
const avancement = Math.round(features
.filter(feature => feature.properties.status === 'done')
.map(feature => feature.properties.distance || 0)
.reduce((acc, current) => acc + current, 0) * 100 / voie.distance)
const features = geojson.value.features;
const doneFeatures = features.filter(feature => feature.properties.status === 'done');
const doneDistance = getDistance({ features: doneFeatures });
const avancement = Math.round(doneDistance / voie.distance * 100);
</script>
70 changes: 51 additions & 19 deletions composables/useStats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ type Feature = {
type: string;
properties: {
id?: string;
line?: string;
color?: string;
name?: string;
distance?: number;
status?: string;
line: string;
color: string;
name: string;
status: string;
};
geometry: {
type: string;
coordinates: number[] | number[][];
coordinates: number[][];
};
};

Expand All @@ -20,7 +19,7 @@ type Geojson = {
};

export const useStats = () => {
function getAllUniqSections(voies: Geojson[]) {
function getAllUniqLineStrings(voies: Geojson[]) {
return voies
.map(voie => voie.features)
.flat()
Expand All @@ -37,19 +36,52 @@ export const useStats = () => {
});
}

function getDistance({ allSections, status }) {
const distanceInMeters = allSections
.filter(feature => status.includes(feature.properties.status))
.reduce((acc, section) => {
if (!section.properties.distance) {
console.log('section >>', section);
return acc;
}
return acc + section.properties.distance;
}, 0);
/**
* distance is in meters
*/
function getDistance({ features }: { features: Feature[] }): number {
return features.reduce((acc: number, feature: Feature) => {
return acc + getLineStringDistance(feature);
}, 0);
}

function getLineStringDistance(feature: Feature) {
if (feature.geometry.type !== 'LineString') {
throw new Error('[getLineStringDistance] Feature must be a LineString');
}

let distance = 0;
const coordinates = feature.geometry.coordinates;

for (let i = 0; i < coordinates.length - 1; i++) {
const [lon1, lat1] = coordinates[i];
const [lon2, lat2] = coordinates[i + 1];
distance += haversine(lat1, lon1, lat2, lon2);
}

return distance;
}

function haversine(lat1: number, lon1: number, lat2: number, lon2: number) {
// Convert latitude and longitude from degrees to radians
const toRadians = (angle: number) => (angle * Math.PI) / 180;
lat1 = toRadians(lat1);
lon1 = toRadians(lon1);
lat2 = toRadians(lat2);
lon2 = toRadians(lon2);

// Haversine formula
const dLat = lat2 - lat1;
const dLon = lon2 - lon1;
const a = Math.sin(dLat / 2) ** 2 + Math.cos(lat1) * Math.cos(lat2) * Math.sin(dLon / 2) ** 2;
const c = 2 * Math.asin(Math.sqrt(a));

// Radius of the Earth in meters
const radius = 6371000;

return Math.round(distanceInMeters / 1000);
// Calculate the distance in meters
return Math.round(radius * c);
}

return { getAllUniqSections, getDistance };
return { getAllUniqLineStrings, getDistance };
};
Loading

0 comments on commit a409a8e

Please sign in to comment.