diff --git a/src/client/api.ts b/src/client/api.ts index 0f39453..5254b6a 100644 --- a/src/client/api.ts +++ b/src/client/api.ts @@ -139,6 +139,11 @@ export const icon_type = (item: string | NominatimResult): string => { "nature_reserve", ], "dice-five": ["water_park", "theme_park", "casino"], + "arrow-right": ["other"], + "train": ["train"], + "car-side":["car"], + "bycicle":["bike"], + "plane":["plane", "flight"], "": ["?", "neighbourhood", "quarter", "highway", "place"], }; diff --git a/src/client/helper/api.ts b/src/client/helper/api.ts index 5eec366..5708e25 100644 --- a/src/client/helper/api.ts +++ b/src/client/helper/api.ts @@ -44,7 +44,7 @@ const process_results = function (tpe: "nominatim" | "travel", r: geoloc[]) { }); case "travel": return r.map((el) => { - (el as any).path = getGeoLine( + el.path = getGeoLine( { lat: (el as any).from_geo.lat, lng: (el as any).from_geo.lon, diff --git a/src/client/old.js b/src/client/old.js index 248a292..3ce4fdc 100644 --- a/src/client/old.js +++ b/src/client/old.js @@ -6,6 +6,7 @@ import { focus_day, focus_leg, nav_mouseleave, nav_mousemove } from "./helper/na import { set_search_set_results } from "./helper/api"; import toast from "./types/toast"; import search_drawer from "./types/search_drawer"; +import { to_xy } from "./types/geom"; Vue.component("l-map", window.Vue2Leaflet.LMap); Vue.component("l-tile-layer", window.Vue2Leaflet.LTileLayer); @@ -36,10 +37,12 @@ const app = new Vue({ generate_rotation: function (index, list) { if (index < 0 || index >= list.length) return 0; - const c0 = list[(index == 0) ? index : (index - 1)] - const c1 = list[(index == (list.length - 1)) ? index : (index + 1)] - const brng = Math.atan2(c1[1] - c0[1], c1[0] - c0[0]); - return `rotate:${brng - Math.PI / 2}rad`; + const c0 = to_xy(list[(index == 0) ? index : (index - 1)]) + const c1 = to_xy(list[(index == (list.length - 1)) ? index : (index + 1)]) + const brng = Math.atan2(c1[1] - c0[1], c1[0] - c0[0])- Math.PI / 2; + const flip = (brng>-Math.PI/2)?'transform: scale(1,1);': 'transform: scale(1, -1);'; + const rot = `rotate:${brng}rad;` + return `${rot}${flip}`; }, generate_marker: function (item, fcolor) { @@ -54,30 +57,6 @@ const app = new Vue({ return ``; }, - - drawer_hover_item: function (item) { - if (item) { - this.search_drawer.map_override.active = true - if (item.type == 'flight') { - this.search_drawer.map_override.elements = [[item.from_geo.lat, item.from_geo.lon], [item.to_geo.lat, item.to_geo.lon]] - } else { - this.search_drawer.map_override.elements = [[item.lat, item.lon]] - } - } else { - this.search_drawer.map_override.active = false - } - }, - - drawer_click_item: function (item) { - const tpe = this.search_drawer.query.type - this.search_drawer.reset(); - this.drawer_hover_item(); - if (item) { - item.step = -1; - journey_add_place(this.journey, tpe, item) - } - }, - refreshTextAreaHeight: function (e) { e.target.style['height'] = 'auto'; e.target.style['height'] = e.target.scrollHeight + 'px'; @@ -91,7 +70,7 @@ const app = new Vue({ sname: this.search_drawer.query.query, type: this.search_drawer.query.type, }; - this.drawer_click_item(newMarker) + this.search_drawer.click_item(this.journey,newMarker) } }, }, @@ -123,7 +102,7 @@ const app = new Vue({ api.load(this.journey.id).then((r) => { app.journey.data = migrator(r) - setTimeout(() => focus_leg(this.journey) && focus_day(this.journey) && this.$refs.map.mapObject.keyboard.disable(), 25); + setTimeout(() => focus_leg(this.journey) && focus_day(this.journey) && this.$refs.map.mapObject.keyboard.disable(), 50); ; this.search_drawer.refresh_map = () => { this.$refs.map.mapObject.invalidateSize() } diff --git a/src/client/types/format.ts b/src/client/types/format.ts index d89a1e6..7819db8 100644 --- a/src/client/types/format.ts +++ b/src/client/types/format.ts @@ -7,10 +7,12 @@ declare global { } interface geoloc { title: string - osm_id: number - latlon: [number, number] notes: string - step: -1 + type?: string + osm_id?: number + latlon?: [number, number] + step?: -1 + path?: number[][] } interface map { diff --git a/src/client/types/geom.ts b/src/client/types/geom.ts index 23a864a..928e94a 100644 --- a/src/client/types/geom.ts +++ b/src/client/types/geom.ts @@ -160,6 +160,15 @@ function recursiveMidPoint(src: LatLng, dst: LatLng, opt: { step?: number, dist? return geom; } -export function getGeoLine(src: LatLng, dst: LatLng, opt: { step?: number, dist?: number }) { +export function to_xy(point: number[]){ + const R = 6378137; // Earth's radius in meters (WGS84 standard) + const lon =((point[0] + 180) % 360 + 360) % 360 - 180; + const lat = ((point[1] + 90) % 180 + 180) % 180 - 90; + const x = R * (lon* Math.PI / 180); + const y = R * Math.log(Math.tan((Math.PI / 4) + (lat * Math.PI / 360))); + return [x, y]; +} + +export function getGeoLine(src: LatLng, dst: LatLng, opt: { step?: number, dist?: number }): LatLng[] { return recursiveMidPoint(src, dst, opt, 1) } \ No newline at end of file diff --git a/src/client/types/migration.ts b/src/client/types/migration.ts index 14f8963..0f96b08 100644 --- a/src/client/types/migration.ts +++ b/src/client/types/migration.ts @@ -8,7 +8,7 @@ function migrate_A_to_0(e: journey): journey { v.day_title = v.day_title || (v as any).step_title; v.places.activities = v.places.activities || (v as any).places.places; v.travel = v.travel || []; - v.day_title = typeof (v.day_title) == "string" ? [v.day_title] : [] + v.day_title = typeof (v.day_title) == "string" ? [v.day_title] : []; }) console.log(e) return e; diff --git a/src/client/types/search_drawer.ts b/src/client/types/search_drawer.ts index 144f372..500ec4e 100644 --- a/src/client/types/search_drawer.ts +++ b/src/client/types/search_drawer.ts @@ -1,5 +1,6 @@ import { search_flight, search_nominatim } from "../helper/api"; import { leg_template } from "./format"; +import journey_wrapper from "./wrapper"; declare global { @@ -10,21 +11,22 @@ declare global { interface query { type: query_type | note_type | "", sub: sub_type | "", - query: String, + query: string, res: any[], - load: Boolean, + load: boolean, /* DELETE BELOW IF POSIBLE */ addmarker: Boolean, } interface map_override { active: Boolean, - elements: geoloc[] + elements: number[][] } } class search_drawer { map_override: map_override = { active: false, elements: [] }; + multipath : any[]=[]; query: query = { type: "", query: "", sub: "", res: [], load: false, addmarker: false, @@ -45,12 +47,21 @@ class search_drawer { is_note() { return ["notes"].includes(this.query.type) } + is_place(){ + return ["hotel", "restaurant", "place"].includes(this.query.type) + } is_query() { - return ["hotel", "restaurant", "place", "flight"].includes(this.query.type) + return this.is_place()|| this.is_flight() || this.is_multipath() } is_sub_travel() { return ["travel"].includes(this.query.sub) } + is_multipath(){ + return ["flight", "train", "car","bike", "other"].includes(this.query.type) + } + is_flight(){ + return ["flight"].includes(this.query.type) + } results(r: any[]) { this.query.load = false; @@ -59,6 +70,8 @@ class search_drawer { } reset() { this.query.res = []; + this.multipath = []; + this.map_override.elements = [] this.query.type = ""; this.query.addmarker = false; setTimeout(() => this.resfresh_map(), 500); @@ -88,6 +101,48 @@ class search_drawer { if (this.is_query()) this.search() } + hover_item (item?:any) { + if (item) { + this.map_override.active = true + if (item.type == 'flight') { + this.map_override.elements.push([item.from_geo.lat, item.from_geo.lon]) + this.map_override.elements.push([item.to_geo.lat, item.to_geo.lon]) + } else { + this.map_override.elements.push([item.lat, item.lon]) + } + } else { + this.map_override.active = false + } + } + + click_item (journey: journey_wrapper, item?:any) { + const tpe = this.query.type + if(this.is_multipath()){ + if(this.query.addmarker){ + if(item){ + this.map_override.active = true + this.map_override.elements.push(item.latlon? item.latlon: [item.lat,item.lon]) + this.multipath.push(item) + return; + }else{ + let new_item :geoloc = { + type: (tpe as string), + path: this.map_override.elements, + title: '-', + notes: "", + } + this.map_override.active = false + journey.add_place(tpe, new_item) + } + } + } + this.reset(); + this.hover_item(); + if (item) { + item.step = -1; + journey.add_place(tpe, item) + } + } } diff --git a/src/client/types/wrapper.ts b/src/client/types/wrapper.ts index 84a6f0c..8a5e75c 100644 --- a/src/client/types/wrapper.ts +++ b/src/client/types/wrapper.ts @@ -174,8 +174,12 @@ class journey_wrapper { case 'hotel': return this.leg_get().hotel = item; case 'restaurant': return this.leg_get().places.restaurants.push(item); case 'place': return this.leg_get().places.activities.push(item); - case 'other': return; + case 'other': + case 'train': + case 'car': + case 'bike': case 'flight': return this.leg_get().travel.push(item); + } } diff --git a/src/template/module/journey/leg/drawer.pug b/src/template/module/journey/leg/drawer.pug index fac98f4..081e88c 100644 --- a/src/template/module/journey/leg/drawer.pug +++ b/src/template/module/journey/leg/drawer.pug @@ -12,26 +12,61 @@ .spinner(v-if="search_drawer.query.load") div(v-if="search_drawer.is_query()") - div(v-if="['flight'].includes(search_drawer.query.type)") + div(v-if="search_drawer.is_flight()") template(v-for="(item, idx) in search_drawer.query.res" ) .query-result.col-12.bg-white.text-dark( :key="'q'+idx" - @mouseover="drawer_hover_item(item)" - @mouseleave="drawer_hover_item()" - @click="drawer_click_item(item)" ) + @mouseover="search_drawer.hover_item(item)" + @mouseleave="search_drawer.hover_item()" + @click="search_drawer.click_item(journey,item)" ) div( v-html="generate_icon('plane', 'var(--dark)')") .col-10() | {{ item.from }} => {{item.to}} .bg-dark.divider( :key="'qdiv'+idx" style="height:1px" ) - + .query-result.col-12.bg-white.text-dark( + v-if="!search_drawer.query.addmarker" + @click="search_drawer.query.addmarker=true" ) + div( v-html="generate_icon('star', 'var(--dark)')") + .col-10() + | Start custom + .query-result.col-12.bg-white.text-dark( + v-else + @click="search_drawer.click_item(journey)" ) + div( v-html="generate_icon('star', 'var(--dark)')") + .col-10() + | Finish + div(v-else-if="search_drawer.is_multipath()") + template(v-for="(item, idx) in search_drawer.query.res" ) + .query-result.col-12.bg-white.text-dark( + :key="'q'+idx" + @mouseover="search_drawer.hover_item(item)" + @mouseleave="search_drawer.hover_item()" + @click="search_drawer.click_item(journey,item)" ) + div( v-html="generate_icon('plane', 'var(--dark)')") + .col-10() + | {{ item.from }} => {{item.to}} + .bg-dark.divider( + :key="'qdiv'+idx" style="height:1px" ) + .query-result.col-12.bg-white.text-dark( + v-if="!search_drawer.query.addmarker" + @click="search_drawer.query.addmarker=true" ) + div( v-html="generate_icon('star', 'var(--dark)')") + .col-10() + | Start custom + .query-result.col-12.bg-white.text-dark( + v-else + @click="search_drawer.click_item(journey)" ) + div( v-html="generate_icon('star', 'var(--dark)')") + .col-10() + | Finish div(v-else-if="true") template(v-for="(item, idx) in search_drawer.query.res" ) .query-result.col-12.bg-white.text-dark( :key="'q'+idx" - @mouseover="drawer_hover_item(item)" - @mouseleave="drawer_hover_item()" - @click="drawer_click_item(item)" ) + @mouseover="search_drawer.hover_item(item)" + @mouseleave="search_drawer.hover_item()" + @click="search_drawer.click_item(journey,item)" ) div( v-html="generate_icon(item, 'var(--dark)')") .col-10() | {{ item.name }} diff --git a/src/template/module/journey/map.pug b/src/template/module/journey/map.pug index 67fc2d8..57a4d7e 100644 --- a/src/template/module/journey/map.pug +++ b/src/template/module/journey/map.pug @@ -4,6 +4,10 @@ l-map( @click="onMapClick" style="height:100%" no-blocking-animations=true + :max-bounds="[[-90, -180],[90, 180]]" + :max-bounds-viscosity="1.0" + :world-copy-jump="true" + :no-wrap="true" :style="search_drawer.query.addmarker?'cursor:crosshair':''" ref="map" ) diff --git a/src/template/module/journey/map/mixin-marker.pug b/src/template/module/journey/map/mixin-marker.pug index a211a64..8270f5f 100644 --- a/src/template/module/journey/map/mixin-marker.pug +++ b/src/template/module/journey/map/mixin-marker.pug @@ -31,7 +31,7 @@ mixin map_marker(place, color_sel_c, color_sel_o, color_else) v-html="generate_icon(((place.step==journey.sel_day)?'calendar-xmark':'calendar-plus'), 'NA')" ) a.text-gray( - v-on:click.prevent="place_delete(journey,\""+place+"\",index)" + v-on:click.prevent="journey.del_place(\""+place+"\",index)" v-html="generate_icon('trash', 'NA')" ) span.row.text-small.text-dark(v-else) {{ place.notes }} diff --git a/src/template/module/journey/map/right_menu.pug b/src/template/module/journey/map/right_menu.pug index dc6498f..8584527 100644 --- a/src/template/module/journey/map/right_menu.pug +++ b/src/template/module/journey/map/right_menu.pug @@ -1,5 +1,5 @@ .map-menu.map-menu-top - div(v-if="search_drawer.is_query()" @click="drawer_click_item()" ) + div(v-if="search_drawer.is_query()" @click="search_drawer.click_item(journey)" ) .map-menu-item(v-html="generate_icon('close')") div(v-if="!search_drawer.is_query()" @click="search_drawer.enable('hotel')") .map-menu-item( v-html="generate_icon('bed')") @@ -8,14 +8,14 @@ div(v-if="!search_drawer.is_query()" @click="search_drawer.enable('place')") .map-menu-item( v-html="generate_icon('star')") .map-menu-sub(v-if="!search_drawer.is_query()" @mouseenter="search_drawer.query.sub='travel'" @mouseleave="search_drawer.query.sub=''" ) - .map-menu-item(v-html="generate_icon('route')") + .map-menu-item(@click="search_drawer.enable('other')" v-html="generate_icon('route')") .map-menu-item(v-if="search_drawer.is_sub_travel()" @click="search_drawer.enable('flight')" v-html="generate_icon('plane')") .map-menu-item(v-if="search_drawer.is_sub_travel()" @click="search_drawer.enable('train')" v-html="generate_icon('train')") .map-menu-item(v-if="search_drawer.is_sub_travel()" @click="search_drawer.enable('car')" v-html="generate_icon('car')") - .map-menu-item(v-if="search_drawer.is_sub_travel()" @click="search_drawer.enable('other')" v-html="generate_icon('person-biking')") + .map-menu-item(v-if="search_drawer.is_sub_travel()" @click="search_drawer.enable('bike')" v-html="generate_icon('person-biking')") .map-menu.map-menu-center - div(v-if="search_drawer.is_note()" @click="drawer_click_item()" ) + div(v-if="search_drawer.is_note()" @click="search_drawer.click_item(journey)" ) .map-menu-item(v-html="generate_icon('close')") div(v-if="!search_drawer.is_note()" @click="search_drawer.enable('notes')") .map-menu-item( v-html="generate_icon('pencil')") \ No newline at end of file diff --git a/src/template/module/journey/map/travel.pug b/src/template/module/journey/map/travel.pug index d9ec316..3c71504 100644 --- a/src/template/module/journey/map/travel.pug +++ b/src/template/module/journey/map/travel.pug @@ -3,7 +3,7 @@ mixin flight_popup() :options="{maxWidth:400, minWidth:300}" ) h1.row.text-medium.text-center.text-uppercase {{ travel.id }} - span.row.text-small.text-gray {{ travel.from }} - {{travel.to}} + span.row.text-small.text-gray {{ travel.title? travel.title:`${travel.from||'?'}-${travel.to||'?'}` }} span(v-if="journey.edit") .row.input(style="margin-bottom:0") textarea.col-12.col-sm-12.text-small( @@ -14,7 +14,7 @@ mixin flight_popup() span(v-if="journey.edit") .leaflet-popup-button-group() a.text-gray( - v-on:click.prevent="place_delete(journey,'flight',idx)" + v-on:click.prevent="journey.del_place('flight',idx)" v-html="generate_icon('trash', 'NA')" ) @@ -24,9 +24,10 @@ div(v-for= "(travel, idx) in journey.leg_get().travel") l-marker( v-for="(place, index) in travel.path" - :key="'plane'+index" + :key="'trvl'+index" :lat-lng="place" + style="margin-left:0;" ) - l-icon(v-html="generate_icon('plane', travel.color || 'gray', generate_rotation(index,travel.path), 'travel-path-icon')" + l-icon(v-html="generate_icon(travel, travel.color || 'gray', generate_rotation(index,travel.path)+'margin:-6px -6px; position:absolute', 'travel-path-icon')" ) +flight_popup()