| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622 |
- //
- // Copyright (c) 2021 Clementine Computing LLC.
- //
- // This file is part of RideLogic.
- //
- // RideLogic is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License as published by
- // the Free Software Foundation, either version 3 of the License, or
- // (at your option) any later version.
- //
- // RideLogic is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with RideLogic. If not, see <https://www.gnu.org/licenses/>.
- //
- var MAX_ICON = 128;
- var g_info = {
- "geojson" : {},
- "route" : {},
- "route_path": {},
- "active_route" : [],
- "ol_route_layer" : {},
- "json_gtfs": {}
- };
- var Feature = ol.Feature;
- var Map = ol.Map;
- var Overlay = ol.Overlay;
- var Point = ol.geom.Point;
- var TileJSON = ol.source.TileJSON;
- var VectorSource = ol.source.Vector;
- var View = ol.View;
- var Icon = ol.style.Icon;
- var Style = ol.style.Style;
- var TileLayer = ol.layer.Tile;
- var VectorLayer = ol.layer.Vector;
- var OSM = ol.source.OSM;
- var fromLonLat = ol.proj.fromLonLat;
- var Control = ol.control.Control;
- var LineString = ol.geom.LineString;
- //var defaultLonLat = [ -76.5019, 42.4440];
- //var defaultLonLat = [ -76.1805, 42.6012 ];
- var defaultLonLat = [ -76.2705, 42.5512 ];
- var defaultWebMerc = fromLonLat(defaultLonLat);
- function _rnd(a,b) {
- a = ((typeof a == "undefined") ? 1.0 : a);
- if (typeof b === "undefined") {
- return Math.random()*a;
- }
- return Math.random()*(b-a) + a;
- }
- function example_change_pos() {
- let w = 1/16.0;
- let lonlat = [
- defaultLonLat[0] + _rnd(-w,w),
- defaultLonLat[1] + _rnd(-w,w)];
- let merc = fromLonLat(lonlat);
- _icon[0].feature.getGeometry().setCoordinates(merc);
- busLayer.getSource().changed();
- }
- function example_change_opacity(p) {
- p = ((typeof p === "undefined") ? 0.0 : p);
- _icon[0].style.getImage().setOpacity(p);
- busLayer.getSource().changed();
- }
- // icon can't change image src dynamically,
- // so have to replace.
- // See https://stackoverflow.com/questions/57341190/how-can-i-change-icon-source-dynamically
- //
- function example_change_image() {
- let _nuimg = new Icon({
- anchor: [0.5, 46],
- anchorXUnits: 'fraction',
- anchorYUnits: 'pixels',
- src: 'data/bus_gw_90.png',
- });
- _icon[0].style.setImage(_nuimg);
- busLayer.getSource().changed();
- }
- //---
- var vectorSource = new VectorSource();
- var g_icon = [];
- for (let ii=0; ii<MAX_ICON; ii++) {
- let w = 1/32.0;
- let lonlat = [
- defaultLonLat[0] + _rnd(-w,w),
- defaultLonLat[1] + _rnd(-w,w)];
- let merc = fromLonLat(lonlat);
- let iconFeature = new Feature({
- geometry: new Point([merc[0], merc[1]]),
- name: 'bus'
- });
- let iconStyle = new Style({
- image: new Icon({
- //scale: [2, 2],
- anchor: [0.5, 46],
- anchorXUnits: 'fraction',
- anchorYUnits: 'pixels',
- src: 'data/icon.png',
- }),
- });
- iconStyle.getImage().setOpacity(0.0);
- g_icon.push({ "style": iconStyle, "feature": iconFeature });
- iconFeature.setStyle(iconStyle);
- vectorSource.addFeature(iconFeature);
- }
- //---
- const busLayer = new VectorLayer({
- source: vectorSource,
- zIndex: 2
- });
- //--
- const rasterLayer = new TileLayer({
- //source: ol.source.OSM()
- source: new ol.source.OSM(),
- });
- //--
- //-------------------
- //-------------------
- //-------------------
- const g_map = new Map({
- //layers: [rasterLayer, vectorLayer, routeLayer],
- layers: [rasterLayer, busLayer ],
- target: document.getElementById('map'),
- controls:[],
- view: new View({
- center: defaultWebMerc,
- zoom: 11,
- }),
- });
- var g_side_toggle = true;
- function toggle_sidebar(tf) {
- g_side_toggle = (g_side_toggle ? false : true);
- if (typeof tf !== "undefined") {
- g_side_toggle = tf;
- }
- // my perpetual fight with CSS.
- // I can't seem to get these values in when I put them in the
- // CSS, so we do it here programatically.
- //
- // Create a scrollable bar only for the list and not
- // for the map.
- // Hide the scroll bar when collapsing.
- // Hide the x scrollbar but dumping x values that overflow.
- //
- let ele = document.getElementById("sidebar-wrapper");
- if (g_side_toggle) {
- ele.style["overflow-y"] = "auto";
- ele.style["overflow-x"] = "hidden";
- ele.style["margin-left"] = 0;
- ele.style["min-width"] = "15rem";
- ele.style["max-height"] = "100vh";
- }
- else {
- ele.style["overflow-y"] = "hidden";
- ele.style["overflow-x"] = "hidden";
- ele.style["margin-left"] = "-15rem";
- ele.style["min-width"] = "0";
- ele.style["max-height"] = "100vh";
- }
- }
- class CustomToggle extends Control {
- constructor(opt) {
- opt = opt || {};
- let ele = document.createElement("button");
- ele.id = 'ui_route_toggle';
- ele.innerHTML = "Route";
- // class="btn" id="sidebarToggle" style='display:float;'
- ele.classList.add("custom-button");
- super({
- element: ele,
- target: opt.target
- });
- ele.addEventListener('click', this.fire.bind(this), false);
- }
- fire() {
- toggle_sidebar();
- // hacky, but functional
- //
- setTimeout(resize_map, 250);
- }
- }
- // button to toggle side bar route list
- //
- g_map.addControl(new CustomToggle({ className: 'custom-button' }));
- //-------------------
- //-------------------
- //-------------------
- g_map.addControl(new ol.control.Zoom({
- className: 'custom-zoom'
- }));
- const element = document.getElementById('popup');
- const popup = new Overlay({
- element: element,
- positioning: 'bottom-center',
- stopEvent: false,
- });
- g_map.addOverlay(popup);
- // display popup on click
- //
- g_map.on('click', function (evt) {
- // not working (wip)
- //
- /*
- const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) {
- return feature;
- });
- */
- });
- // change mouse cursor when over marker
- g_map.on('pointermove', function (e) {
- const pixel = g_map.getEventPixel(e.originalEvent);
- const hit = g_map.hasFeatureAtPixel(pixel);
- g_map.getTarget().style.cursor = hit ? 'pointer' : '';
- });
- // Close the popup when the map is moved
- //
- g_map.on('movestart', function () {
- //$(element).popover('dispose');
- });
- g_map.on("postrender", function(event) {
- //console.log("bang...");
- });
- //-----
- function update_buses(json_gtfs) {
- let bearingIdxLookup = [ "0", "45", "90", "135", "180", "225", "270", "315" ];
- for (let ii=0; ii<json_gtfs.entityList.length; ii++) {
- let id = json_gtfs.entityList[ii].id;
- let p = json_gtfs.entityList[ii].vehicle.position;
- let lat = p.latitude;
- let lon = p.longitude;
- let bearing = p.bearing;
- let route_id = -1;
- let route_str = "";
- if ((typeof json_gtfs.entityList[ii] !== "undefined") &&
- (typeof json_gtfs.entityList[ii].vehicle !== "undefined") &&
- (typeof json_gtfs.entityList[ii].vehicle.trip !== "undefined") &&
- (typeof json_gtfs.entityList[ii].vehicle.trip.routeId !== "undefined")) {
- route_str = "_r" + json_gtfs.entityList[ii].vehicle.trip.routeId;
- route_id = json_gtfs.entityList[ii].vehicle.trip.routeId;
- }
- else { }
- let lonlat = [ lon , lat ];
- let merc = fromLonLat(lonlat);
- if (typeof g_icon[ii] === "undefined") { continue; }
- g_icon[ii].feature.getGeometry().setCoordinates(merc);
- bearing = (bearing % 360 );
- if (bearing < 0) { bearing += 360.0; }
- let iheading = Math.floor( (parseInt( bearing ) + 23) / 45 );
- if (iheading > 7) { iheading = 0; }
- let bearing_str = "_stat"
- if (p.speed != 0) {
- bearing_str = "_" + bearingIdxLookup[iheading];
- }
- let _nuimg = new Icon({
- //scale: [1.5, 1.5],
- anchor: [0.5, 46],
- anchorXUnits: 'fraction',
- anchorYUnits: 'pixels',
- //src: 'data/bus_gw' + route_str + "_" + bearingIdxLookup[iheading] + '.png',
- src: 'data/bus_gw' + route_str + bearing_str + '.png',
- });
- g_icon[ii].style.setImage(_nuimg);
- if (g_info.active_route.length == 0) {
- g_icon[ii].style.getImage().setOpacity(0.8);
- }
- else {
- let is_active = false;
- for (let _aridx=0; _aridx < g_info.active_route.length; _aridx++) {
- if ( g_info.active_route[_aridx] == route_id) {
- is_active = true;
- }
- }
- if (is_active) {
- g_icon[ii].style.getImage().setOpacity(0.8);
- }
- else {
- g_icon[ii].style.getImage().setOpacity(0.0);
- }
- }
- }
- for (let ii=json_gtfs.entityList.length; ii<MAX_ICON; ii++) {
- g_icon[ii].style.getImage().setOpacity(0.0);
- }
- busLayer.getSource().changed();
- }
- function fetch_gtfs_vehicle_position() {
- let xhr = new XMLHttpRequest();
- xhr.onload = function() {
- let ab = xhr.response;
- let json_pb = pb2json.pb2json(ab);
- g_info.json_gtfs = json_pb;
- update_buses(json_pb)
- };
- xhr.responseType = "arraybuffer";
- xhr.open("GET", "VehiclePosition.pb");
- xhr.send();
- }
- //--
- function ui_clear_routes() {
- for (let i=0; i<g_info.active_route; i++) {
- let route_id = g_info.active_route[i];
- let route_layer = g_info.ol_route_layer[route_id];
- g_map.removeLayer( route_layer );
- }
- g_info.active_route = [ ];
- }
- function ui_show_single_route(route_id) {
- if (!(route_id in g_info.ol_route_layer)) {
- console.log("route", route_id, "not found in g_info.ol_route_layer");
- return;
- }
- for (let i=0; i<g_info.active_route; i++) {
- let route_id = g_info.active_route[i];
- let route_layer = g_info.ol_route_layer[route_id];
- g_map.removeLayer( route_layer );
- }
- g_info.active_route = [ route_id ];
- g_map.addLayer( g_info.ol_route_layer[route_id] );
- }
- function init_route_layer() {
- for (let route_id in g_info.route_path) {
- let route_points = g_info.route_path[route_id];
- let merc_points = [];
- for (let ii=0; ii<route_points.length; ii++) {
- merc_points.push(ol.proj.transform(route_points[ii], 'EPSG:4326', 'EPSG:3857'));
- }
- let routeLine = new ol.Feature({
- geometry: new ol.geom.LineString(merc_points)
- });
- let routeLineVector = new ol.source.Vector({});
- routeLineVector.addFeature(routeLine);
- let routeLayer = new VectorLayer({
- source: routeLineVector,
- style: new ol.style.Style({
- fill: new ol.style.Fill({ color: 'rgb(0,0,255,0.25)', weight: 4, opacity: 0.5 }),
- stroke: new ol.style.Stroke({ color: 'rgb(0,0,255,0.25)', width: 6, opacity: 0.5 })
- }),
- zIndex: 0
- });
- g_info.ol_route_layer[route_id] = routeLayer;
- }
- }
- function init_stop_layer(geojson) {
- //DEBUG
- return;
- if (!geojson) { return; }
- let stop_points = geojson.features[0].properties.route_stops;
- if (!stop_points) { return; }
- let merc_points = [];
- for (let ii=0; ii<stop_points.length; ii++) {
- let pnt = stop_points[ii].stop.geometry.coordinates;
- merc_points.push(ol.proj.transform(pnt, 'EPSG:4326', 'EPSG:3857'));
- }
- let stopVectorSource = new VectorSource();
- for (let ii=0; ii<merc_points.length; ii++) {
- let _stop = stop_points[ii].stop;
- let _stop_name = _stop.stop_name;
- let iconFeature = new Feature({
- geometry: new Point([merc_points[ii][0], merc_points[ii][1]]),
- name: _stop_name
- });
- let iconStyle = new Style({
- image: new Icon({
- anchor: [0.5, 46],
- anchorXUnits: 'fraction',
- anchorYUnits: 'pixels',
- src: 'data/star-3.png',
- }),
- });
- iconStyle.getImage().setOpacity(0.8);
- iconFeature.setStyle(iconStyle);
- stopVectorSource.addFeature(iconFeature);
- }
- let stopLayer = new VectorLayer({
- source: stopVectorSource,
- zIndex: 0
- });
- g_map.addLayer(stopLayer);
- }
- function fetch_geojson(url) {
- let xhr = new XMLHttpRequest();
- xhr.onload = function() {
- let geojson = xhr.response;
- g_info.geojson = geojson;
- //post_process_geojson();
- ui_add_route();
- init_route_layer(geojson);
- init_stop_layer(geojson);
- };
- xhr.responseType = "json";
- xhr.open("GET", url, true);
- xhr.send();
- }
- //---
- function post_process_geojson() {
- let trip = g_info.geojson.features;
- for (let i=0; i<trip.length; i++) {
- if (trip[i].type != "trip") { continue; }
- let prop = trip[i].properties;
- let _route = prop.route;
- if (_route.route_id in g_info.route) { continue; }
- g_info.route[_route.route_id] = prop.route_stops;
- g_info.route_path[_route.route_id] = trip[i].geometry.coordinates;
- }
- }
- // add routes to side menu
- //
- function ui_add_route() {
- let ele = document.getElementById("ui_route_list");
- ele.innerHTML = '';
- let _route_list = [];
- let _route_long_name = {};
- let trip = g_info.geojson.features;
- for (let i=0; i<trip.length; i++) {
- if (trip[i].type != "trip") { continue; }
- let prop = trip[i].properties;
- let _route = prop.route;
- if (_route.route_id in g_info.route) { continue; }
- g_info.route[_route.route_id] = prop.route_stops;
- g_info.route_path[_route.route_id] = trip[i].geometry.coordinates;
- _route_list.push( _route.route_id );
- _route_long_name[ _route.route_id ] = prop.route.route_long_name;
- }
- _route_list.sort( (a,b) => {
- let _a = parseInt(a), _b = parseInt(b);
- if (a<b) { return -1; }
- if (a>b) { return 1; }
- return 0;
- });
- for (let i=0; i<_route_list.length; i++) {
- let _rln = _route_list[i];
- let _a = document.createElement("a");
- _a.classList.add("list-group-item");
- _a.classList.add("list-gorup-item-action");
- _a.classList.add("list-gorup-item-light");
- _a.classList.add("p-3");
- _a.innerText = _route_long_name[_rln];
- _a.onclick = (function(_r) {
- return function() {
- if ( g_info.active_route == _r) {
- ui_clear_routes();
- update_buses(g_info.json_gtfs);
- }
- else {
- ui_show_single_route(_r);
- update_buses(g_info.json_gtfs);
- }
- }
- })(_route_list[i]);
- ele.appendChild(_a);
- }
- toggle_sidebar(g_side_toggle);
- }
- //---
- // resize map
- //
- function resize_map() {
- let h = $(window).height();
- let m = document.getElementById("map");
- m.style.height = (h-10) + "px";
- g_map.updateSize();
- }
- function init() {
- setInterval(fetch_gtfs_vehicle_position, 1000);
- setTimeout( function() { fetch_geojson("geojson/test_route.geojson"); }, 100);
- $(window).on('resize', resize_map);
- }
- $(document).ready(function() {
- init();
- resize_map();
- toggle_sidebar(g_side_toggle);
- });
|