if an event has geo coordinates, add links to routing on OSM, with correct place and zoom of 14, 3 buttons to get routig as car, bike, and by feet.

Signed-off-by: Baptiste Lemoine <contact@cipherbliss.com>
This commit is contained in:
ty kayn 2020-12-17 11:26:25 +01:00 committed by Thomas Citharel
parent c8fb5bb80e
commit 5c57f1ce3c
15 changed files with 250 additions and 27 deletions

View file

@ -210,6 +210,9 @@ config :mobilizon, :maps,
tiles: [
endpoint: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
attribution: "© The OpenStreetMap Contributors"
],
routing: [
type: :openstreetmap
]
config :mobilizon, :anonymous,

View file

@ -238,6 +238,12 @@ type Tag {
related: [Tag]
}
"Instance map routing configuration"
type Routing {
"The instance's routing type"
type: RoutingType
}
"Language information"
type Language {
"The iso-639-3 language code"
@ -420,6 +426,9 @@ type Events {
type Maps {
"The instance's maps tiles configuration"
tiles: Tiles
"The instance's maps routing configuration"
routing: Routing
}
"Search groups result"
@ -659,6 +668,14 @@ interface Interactable {
url: String
}
enum RoutingType {
"Redirect to openstreetmap.org's direction endpoint"
OPENSTREETMAP
"Redirect to Google Maps's direction endpoint"
GOOGLE_MAPS
}
"A struct containing the id of the deleted object"
type DeletedObject {
id: ID
@ -2856,7 +2873,7 @@ type PaginatedGroupList {
total: Int
}
"Instance tiles configuration"
"Instance map tiles configuration"
type Tiles {
"The instance's tiles endpoint"
endpoint: String

View file

@ -51,6 +51,9 @@ export const CONFIG = gql`
endpoint
attribution
}
routing {
type
}
}
geocoding {
provider

View file

@ -813,12 +813,15 @@
"View all events": "View all events",
"You will find here all the events you have created or of which you are a participant.": "You will find here all the events you have created or of which you are a participant.",
"Create event": "Create event",
"You didn't create or join any event yet": "You didn't create or join any event yet",
"You didn't create or join any event yet.": "You didn't create or join any event yet.",
"create an event": "create an event",
"explore the events": "explore the events",
"Do you wish to {create_event} or {explore_events}?": "Do you wish to {create_event} or {explore_events}?",
"You are not part of any group": "You are not part of any group",
"You are not part of any group.": "You are not part of any group.",
"create a group": "create a group",
"explore the groups": "explore the groups",
"Do you wish to {create_group} or {explore_groups}?": "Do you wish to {create_group} or {explore_groups}?"
"Do you wish to {create_group} or {explore_groups}?": "Do you wish to {create_group} or {explore_groups}?",
"Type or select a date…": "Type or select a date…",
"Getting there": "Getting there",
"Groups are not enabled on this instance.": "Groups are not enabled on this instance."
}

View file

@ -914,5 +914,8 @@
"You are not part of any group.": "Vous ne faites partie d'aucun groupe.",
"create a group": "créer un groupe",
"explore the groups": "explorer les groupes",
"Do you wish to {create_group} or {explore_groups}?": "Voulez-vous {create_group} ou {explore_groups} ?"
"Do you wish to {create_group} or {explore_groups}?": "Voulez-vous {create_group} ou {explore_groups} ?",
"Type or select a date…": "Entrez ou sélectionnez une date…",
"Getting there": "S'y rendre",
"Groups are not enabled on this instance.": "Les groupes ne sont pas activés sur cette instance."
}

View file

@ -15,6 +15,12 @@ export interface IAddress {
originId?: string;
}
export interface IPoiInfo {
name: string;
alternativeName: string;
poiIcon: IPOIIcon;
}
export class Address implements IAddress {
country = "";
@ -54,7 +60,7 @@ export class Address implements IAddress {
this.originId = hash.originId;
}
get poiInfos(): { name: string; alternativeName: string; poiIcon: IPOIIcon } {
get poiInfos(): IPoiInfo {
/* generate name corresponding to poi type */
let name = "";
let alternativeName = "";

View file

@ -1,4 +1,4 @@
import { InstancePrivacyType, InstanceTermsType } from "./enums";
import { InstancePrivacyType, InstanceTermsType, RoutingType } from "./enums";
import type { IProvider } from "./resource";
export interface IOAuthProvider {
@ -58,6 +58,9 @@ export interface IConfig {
endpoint: string;
attribution: string | null;
};
routing: {
type: RoutingType;
};
};
geocoding: {
provider: string;

View file

@ -160,3 +160,15 @@ export enum Openness {
MODERATED = "MODERATED",
OPEN = "OPEN",
}
export enum RoutingType {
OPENSTREETMAP = "OPENSTREETMAP",
GOOGLE_MAPS = "GOOGLE_MAPS",
}
export enum RoutingTransportationType {
FOOT = "FOOT",
BIKE = "BIKE",
TRANSIT = "TRANSIT",
CAR = "CAR",
}

182
js/src/views/Event/Event.vue Normal file → Executable file
View file

@ -84,9 +84,9 @@
<tag>{{ tag.title }}</tag>
</router-link>
</p>
<b-tag type="is-warning" size="is-medium" v-if="event.draft">{{
$t("Draft")
}}</b-tag>
<b-tag type="is-warning" size="is-medium" v-if="event.draft"
>{{ $t("Draft") }}
</b-tag>
<span
class="event-status"
v-if="event.status !== EventStatus.CONFIRMED"
@ -325,7 +325,7 @@
<span
class="map-show-button"
@click="showMap = !showMap"
v-if="physicalAddress && physicalAddress.geom"
v-if="physicalAddress.geom"
>{{ $t("Show map") }}</span
>
</div>
@ -526,8 +526,8 @@
class="button"
ref="cancelButton"
@click="isJoinConfirmationModalActive = false"
>{{ $t("Cancel") }}</b-button
>
>{{ $t("Cancel") }}
</b-button>
<b-button type="is-primary" native-type="submit">
{{ $t("Confirm my participation") }}
</b-button>
@ -537,17 +537,77 @@
</div>
</b-modal>
<b-modal
class="map-modal"
v-if="physicalAddress && physicalAddress.geom"
:active.sync="showMap"
has-modal-card
full-screen
>
<div class="map">
<map-leaflet
:coords="physicalAddress.geom"
:marker="{
text: physicalAddress.fullName,
icon: physicalAddress.poiInfos.poiIcon.icon,
}"
/>
<div class="modal-card">
<header class="modal-card-head">
<button type="button" class="delete" @click="showMap = false" />
</header>
<div class="modal-card-body">
<section class="map">
<map-leaflet
:coords="physicalAddress.geom"
:marker="{
text: physicalAddress.fullName,
icon: physicalAddress.poiInfos.poiIcon.icon,
}"
/>
</section>
<section class="columns is-centered map-footer">
<div class="column is-half has-text-centered">
<p class="address">
<i class="mdi mdi-map-marker"></i>
{{ physicalAddress.fullName }}
</p>
<p class="getting-there">{{ $t("Getting there") }}</p>
<div
class="buttons"
v-if="
addressLinkToRouteByCar ||
addressLinkToRouteByBike ||
addressLinkToRouteByFeet
"
>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByFeet"
:href="addressLinkToRouteByFeet"
>
<i class="mdi mdi-walk"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByBike"
:href="addressLinkToRouteByBike"
>
<i class="mdi mdi-bike"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByTransit"
:href="addressLinkToRouteByTransit"
>
<i class="mdi mdi-bus"></i
></a>
<a
class="button"
target="_blank"
v-if="addressLinkToRouteByCar"
:href="addressLinkToRouteByCar"
>
<i class="mdi mdi-car"></i>
</a>
</div>
</div>
</section>
</div>
</div>
</b-modal>
</div>
@ -563,6 +623,8 @@ import {
EventStatus,
EventVisibility,
ParticipantRole,
RoutingTransportationType,
RoutingType,
} from "@/types/enums";
import {
EVENT_PERSON_PARTICIPATION,
@ -602,6 +664,7 @@ import ActorCard from "../../components/Account/ActorCard.vue";
import PopoverActorCard from "../../components/Account/PopoverActorCard.vue";
import { IParticipant } from "../../types/participant.model";
// noinspection TypeScriptValidateTypes
@Component({
components: {
EventMetadataBlock,
@ -734,6 +797,65 @@ export default class Event extends EventMixin {
messageForConfirmation = "";
RoutingParamType = {
[RoutingType.OPENSTREETMAP]: {
[RoutingTransportationType.FOOT]: "engine=fossgis_osrm_foot",
[RoutingTransportationType.BIKE]: "engine=fossgis_osrm_bike",
[RoutingTransportationType.TRANSIT]: null,
[RoutingTransportationType.CAR]: "engine=fossgis_osrm_car",
},
[RoutingType.GOOGLE_MAPS]: {
[RoutingTransportationType.FOOT]: "dirflg=w",
[RoutingTransportationType.BIKE]: "dirflg=b",
[RoutingTransportationType.TRANSIT]: "dirflg=r",
[RoutingTransportationType.CAR]: "driving",
},
};
makeNavigationPath(
transportationType: RoutingTransportationType
): string | undefined {
const geometry = this.physicalAddress?.geom;
if (geometry) {
const routingType = this.config.maps.routing.type;
/**
* build urls to routing map
*/
if (!this.RoutingParamType[routingType][transportationType]) {
return;
}
const urlGeometry = geometry.split(";").reverse().join(",");
switch (routingType) {
case RoutingType.GOOGLE_MAPS:
return `https://maps.google.com/?saddr=Current+Location&daddr=${urlGeometry}&${this.RoutingParamType[routingType][transportationType]}`;
case RoutingType.OPENSTREETMAP:
default: {
const bboxX = geometry.split(";").reverse()[0];
const bboxY = geometry.split(";").reverse()[1];
return `https://www.openstreetmap.org/directions?from=&to=${urlGeometry}&${this.RoutingParamType[routingType][transportationType]}#map=14/${bboxX}/${bboxY}`;
}
}
}
}
get addressLinkToRouteByCar(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.CAR);
}
get addressLinkToRouteByBike(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.BIKE);
}
get addressLinkToRouteByFeet(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.FOOT);
}
get addressLinkToRouteByTransit(): undefined | string {
return this.makeNavigationPath(RoutingTransportationType.TRANSIT);
}
get eventTitle(): undefined | string {
if (!this.event) return undefined;
return this.event.title;
@ -1096,6 +1218,7 @@ export default class Event extends EventMixin {
get physicalAddress(): Address | null {
if (!this.event.physicalAddress) return null;
return new Address(this.event.physicalAddress);
}
@ -1225,6 +1348,7 @@ div.sidebar {
a {
text-decoration: none;
}
span {
&.tag {
margin: 0 2px;
@ -1389,9 +1513,31 @@ a.participations-link {
font-size: 1rem;
}
div.map {
height: 900px;
width: 100%;
padding: 25px 5px 0;
.map-modal {
.modal-card-head {
justify-content: flex-end;
button.delete {
margin-right: 1rem;
}
}
section.map {
height: calc(100% - 8rem);
width: calc(100% - 20px);
}
section.map-footer {
p.address {
margin: 1rem auto;
}
div.buttons {
justify-content: center;
}
}
}
.no-border {
border: 0;
cursor: auto;
}
</style>

View file

@ -127,7 +127,7 @@
</span>
</template>
<b-message v-if="config && !config.features.groups" type="is-danger">
{{ $t("Groups are not enabled on your server.") }}
{{ $t("Groups are not enabled on this instance.") }}
</b-message>
<div v-else-if="searchGroups.total > 0">
<div class="columns is-multiline">

View file

@ -56,6 +56,9 @@ export const configMock = {
attribution: "© The OpenStreetMap Contributors",
endpoint: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
},
routing: {
type: "OPENSTREETMAP",
},
},
name: "Mobilizon",
registrationsAllowlist: false,

View file

@ -117,6 +117,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Config do
tiles: %{
endpoint: Config.instance_maps_tiles_endpoint(),
attribution: Config.instance_maps_tiles_attribution()
},
routing: %{
type: Config.instance_maps_routing_type()
}
},
resource_providers: Config.instance_resource_providers(),

View file

@ -106,16 +106,29 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
"""
object :maps do
field(:tiles, :tiles, description: "The instance's maps tiles configuration")
field(:routing, :routing, description: "The instance's maps routing configuration")
end
@desc """
Instance tiles configuration
Instance map tiles configuration
"""
object :tiles do
field(:endpoint, :string, description: "The instance's tiles endpoint")
field(:attribution, :string, description: "The instance's tiles attribution text")
end
@desc """
Instance map routing configuration
"""
object :routing do
field(:type, :routing_type, description: "The instance's routing type")
end
enum :routing_type do
value(:openstreetmap, description: "Redirect to openstreetmap.org's direction endpoint")
value(:google_maps, description: "Redirect to Google Maps's direction endpoint")
end
@desc """
Instance anonymous configuration
"""

View file

@ -151,6 +151,10 @@ defmodule Mobilizon.Config do
def instance_maps_tiles_attribution,
do: Application.get_env(:mobilizon, :maps)[:tiles][:attribution]
@spec instance_maps_routing_type :: atom()
def instance_maps_routing_type,
do: Application.get_env(:mobilizon, :maps)[:routing][:type]
@spec anonymous_participation? :: boolean
def anonymous_participation?,
do: Application.get_env(:mobilizon, :anonymous)[:participation][:allowed]

4
yarn.lock Normal file
View file

@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1