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: [ tiles: [
endpoint: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", endpoint: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
attribution: "© The OpenStreetMap Contributors" attribution: "© The OpenStreetMap Contributors"
],
routing: [
type: :openstreetmap
] ]
config :mobilizon, :anonymous, config :mobilizon, :anonymous,

View file

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

View file

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

View file

@ -813,12 +813,15 @@
"View all events": "View all events", "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.", "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", "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", "create an event": "create an event",
"explore the events": "explore the events", "explore the events": "explore the events",
"Do you wish to {create_event} or {explore_events}?": "Do you wish to {create_event} or {explore_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", "create a group": "create a group",
"explore the groups": "explore the groups", "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.", "You are not part of any group.": "Vous ne faites partie d'aucun groupe.",
"create a group": "créer un groupe", "create a group": "créer un groupe",
"explore the groups": "explorer les groupes", "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; originId?: string;
} }
export interface IPoiInfo {
name: string;
alternativeName: string;
poiIcon: IPOIIcon;
}
export class Address implements IAddress { export class Address implements IAddress {
country = ""; country = "";
@ -54,7 +60,7 @@ export class Address implements IAddress {
this.originId = hash.originId; this.originId = hash.originId;
} }
get poiInfos(): { name: string; alternativeName: string; poiIcon: IPOIIcon } { get poiInfos(): IPoiInfo {
/* generate name corresponding to poi type */ /* generate name corresponding to poi type */
let name = ""; let name = "";
let alternativeName = ""; let alternativeName = "";

View file

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

View file

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

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

@ -84,9 +84,9 @@
<tag>{{ tag.title }}</tag> <tag>{{ tag.title }}</tag>
</router-link> </router-link>
</p> </p>
<b-tag type="is-warning" size="is-medium" v-if="event.draft">{{ <b-tag type="is-warning" size="is-medium" v-if="event.draft"
$t("Draft") >{{ $t("Draft") }}
}}</b-tag> </b-tag>
<span <span
class="event-status" class="event-status"
v-if="event.status !== EventStatus.CONFIRMED" v-if="event.status !== EventStatus.CONFIRMED"
@ -325,7 +325,7 @@
<span <span
class="map-show-button" class="map-show-button"
@click="showMap = !showMap" @click="showMap = !showMap"
v-if="physicalAddress && physicalAddress.geom" v-if="physicalAddress.geom"
>{{ $t("Show map") }}</span >{{ $t("Show map") }}</span
> >
</div> </div>
@ -526,8 +526,8 @@
class="button" class="button"
ref="cancelButton" ref="cancelButton"
@click="isJoinConfirmationModalActive = false" @click="isJoinConfirmationModalActive = false"
>{{ $t("Cancel") }}</b-button >{{ $t("Cancel") }}
> </b-button>
<b-button type="is-primary" native-type="submit"> <b-button type="is-primary" native-type="submit">
{{ $t("Confirm my participation") }} {{ $t("Confirm my participation") }}
</b-button> </b-button>
@ -537,10 +537,18 @@
</div> </div>
</b-modal> </b-modal>
<b-modal <b-modal
class="map-modal"
v-if="physicalAddress && physicalAddress.geom" v-if="physicalAddress && physicalAddress.geom"
:active.sync="showMap" :active.sync="showMap"
has-modal-card
full-screen
> >
<div class="map"> <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 <map-leaflet
:coords="physicalAddress.geom" :coords="physicalAddress.geom"
:marker="{ :marker="{
@ -548,6 +556,58 @@
icon: physicalAddress.poiInfos.poiIcon.icon, 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> </div>
</b-modal> </b-modal>
</div> </div>
@ -563,6 +623,8 @@ import {
EventStatus, EventStatus,
EventVisibility, EventVisibility,
ParticipantRole, ParticipantRole,
RoutingTransportationType,
RoutingType,
} from "@/types/enums"; } from "@/types/enums";
import { import {
EVENT_PERSON_PARTICIPATION, EVENT_PERSON_PARTICIPATION,
@ -602,6 +664,7 @@ import ActorCard from "../../components/Account/ActorCard.vue";
import PopoverActorCard from "../../components/Account/PopoverActorCard.vue"; import PopoverActorCard from "../../components/Account/PopoverActorCard.vue";
import { IParticipant } from "../../types/participant.model"; import { IParticipant } from "../../types/participant.model";
// noinspection TypeScriptValidateTypes
@Component({ @Component({
components: { components: {
EventMetadataBlock, EventMetadataBlock,
@ -734,6 +797,65 @@ export default class Event extends EventMixin {
messageForConfirmation = ""; 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 { get eventTitle(): undefined | string {
if (!this.event) return undefined; if (!this.event) return undefined;
return this.event.title; return this.event.title;
@ -1096,6 +1218,7 @@ export default class Event extends EventMixin {
get physicalAddress(): Address | null { get physicalAddress(): Address | null {
if (!this.event.physicalAddress) return null; if (!this.event.physicalAddress) return null;
return new Address(this.event.physicalAddress); return new Address(this.event.physicalAddress);
} }
@ -1225,6 +1348,7 @@ div.sidebar {
a { a {
text-decoration: none; text-decoration: none;
} }
span { span {
&.tag { &.tag {
margin: 0 2px; margin: 0 2px;
@ -1389,9 +1513,31 @@ a.participations-link {
font-size: 1rem; font-size: 1rem;
} }
div.map { .map-modal {
height: 900px; .modal-card-head {
width: 100%; justify-content: flex-end;
padding: 25px 5px 0; 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> </style>

View file

@ -127,7 +127,7 @@
</span> </span>
</template> </template>
<b-message v-if="config && !config.features.groups" type="is-danger"> <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> </b-message>
<div v-else-if="searchGroups.total > 0"> <div v-else-if="searchGroups.total > 0">
<div class="columns is-multiline"> <div class="columns is-multiline">

View file

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

View file

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

View file

@ -106,16 +106,29 @@ defmodule Mobilizon.GraphQL.Schema.ConfigType do
""" """
object :maps do object :maps do
field(:tiles, :tiles, description: "The instance's maps tiles configuration") field(:tiles, :tiles, description: "The instance's maps tiles configuration")
field(:routing, :routing, description: "The instance's maps routing configuration")
end end
@desc """ @desc """
Instance tiles configuration Instance map tiles configuration
""" """
object :tiles do object :tiles do
field(:endpoint, :string, description: "The instance's tiles endpoint") field(:endpoint, :string, description: "The instance's tiles endpoint")
field(:attribution, :string, description: "The instance's tiles attribution text") field(:attribution, :string, description: "The instance's tiles attribution text")
end 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 """ @desc """
Instance anonymous configuration Instance anonymous configuration
""" """

View file

@ -151,6 +151,10 @@ defmodule Mobilizon.Config do
def instance_maps_tiles_attribution, def instance_maps_tiles_attribution,
do: Application.get_env(:mobilizon, :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 @spec anonymous_participation? :: boolean
def anonymous_participation?, def anonymous_participation?,
do: Application.get_env(:mobilizon, :anonymous)[:participation][:allowed] 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