RideLogicAVLS.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542
  1. //
  2. // Copyright (c) 2021 Clementine Computing LLC.
  3. //
  4. // This file is part of RideLogic.
  5. //
  6. // RideLogic is free software: you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // RideLogic is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with RideLogic. If not, see <https://www.gnu.org/licenses/>.
  18. //
  19. var g_info = {
  20. "geojson" : {},
  21. "route" : {},
  22. "route_path": {},
  23. "active_route" : [],
  24. "ol_route_layer" : {}
  25. };
  26. var Feature = ol.Feature;
  27. var Map = ol.Map;
  28. var Overlay = ol.Overlay;
  29. var Point = ol.geom.Point;
  30. var TileJSON = ol.source.TileJSON;
  31. var VectorSource = ol.source.Vector;
  32. var View = ol.View;
  33. var Icon = ol.style.Icon;
  34. var Style = ol.style.Style;
  35. var TileLayer = ol.layer.Tile;
  36. var VectorLayer = ol.layer.Vector;
  37. var OSM = ol.source.OSM;
  38. var fromLonLat = ol.proj.fromLonLat;
  39. var Control = ol.control.Control;
  40. var LineString = ol.geom.LineString;
  41. //var defaultLonLat = [ -76.5019, 42.4440];
  42. //var defaultLonLat = [ -76.1805, 42.6012 ];
  43. var defaultLonLat = [ -76.2705, 42.5512 ];
  44. var defaultWebMerc = fromLonLat(defaultLonLat);
  45. function _rnd(a,b) {
  46. a = ((typeof a == "undefined") ? 1.0 : a);
  47. if (typeof b === "undefined") {
  48. return Math.random()*a;
  49. }
  50. return Math.random()*(b-a) + a;
  51. }
  52. function example_change_pos() {
  53. let w = 1/16.0;
  54. let lonlat = [
  55. defaultLonLat[0] + _rnd(-w,w),
  56. defaultLonLat[1] + _rnd(-w,w)];
  57. let merc = fromLonLat(lonlat);
  58. _icon[0].feature.getGeometry().setCoordinates(merc);
  59. busLayer.getSource().changed();
  60. }
  61. function example_change_opacity(p) {
  62. p = ((typeof p === "undefined") ? 0.0 : p);
  63. _icon[0].style.getImage().setOpacity(p);
  64. busLayer.getSource().changed();
  65. }
  66. // icon can't change image src dynamically,
  67. // so have to replace.
  68. // See https://stackoverflow.com/questions/57341190/how-can-i-change-icon-source-dynamically
  69. //
  70. function example_change_image() {
  71. let _nuimg = new Icon({
  72. anchor: [0.5, 46],
  73. anchorXUnits: 'fraction',
  74. anchorYUnits: 'pixels',
  75. src: 'data/bus_gw_90.png',
  76. });
  77. _icon[0].style.setImage(_nuimg);
  78. busLayer.getSource().changed();
  79. }
  80. //---
  81. var vectorSource = new VectorSource();
  82. var _icon = [];
  83. for (let ii=0; ii<2; ii++) {
  84. let w = 1/32.0;
  85. let lonlat = [
  86. defaultLonLat[0] + _rnd(-w,w),
  87. defaultLonLat[1] + _rnd(-w,w)];
  88. let merc = fromLonLat(lonlat);
  89. let iconFeature = new Feature({
  90. geometry: new Point([merc[0], merc[1]]),
  91. name: 'bus'
  92. });
  93. let iconStyle = new Style({
  94. image: new Icon({
  95. anchor: [0.5, 46],
  96. anchorXUnits: 'fraction',
  97. anchorYUnits: 'pixels',
  98. src: 'data/icon.png',
  99. }),
  100. });
  101. iconStyle.getImage().setOpacity(0.0);
  102. _icon.push({ "style": iconStyle, "feature": iconFeature });
  103. iconFeature.setStyle(iconStyle);
  104. vectorSource.addFeature(iconFeature);
  105. }
  106. //---
  107. const busLayer = new VectorLayer({
  108. source: vectorSource,
  109. zIndex: 2
  110. });
  111. //--
  112. const rasterLayer = new TileLayer({
  113. //source: ol.source.OSM()
  114. source: new ol.source.OSM(),
  115. });
  116. //--
  117. //-------------------
  118. //-------------------
  119. //-------------------
  120. const g_map = new Map({
  121. //layers: [rasterLayer, vectorLayer, routeLayer],
  122. layers: [rasterLayer, busLayer ],
  123. target: document.getElementById('map'),
  124. controls:[],
  125. view: new View({
  126. center: defaultWebMerc,
  127. zoom: 11,
  128. }),
  129. });
  130. var g_side_toggle = true;
  131. function toggle_sidebar(tf) {
  132. g_side_toggle = (g_side_toggle ? false : true);
  133. if (typeof tf !== "undefined") {
  134. g_side_toggle = tf;
  135. }
  136. // my perpetual fight with CSS.
  137. // I can't seem to get these values in when I put them in the
  138. // CSS, so we do it here programatically.
  139. //
  140. // Create a scrollable bar only for the list and not
  141. // for the map.
  142. // Hide the scroll bar when collapsing.
  143. // Hide the x scrollbar but dumping x values that overflow.
  144. //
  145. let ele = document.getElementById("sidebar-wrapper");
  146. if (g_side_toggle) {
  147. ele.style["overflow-y"] = "auto";
  148. ele.style["overflow-x"] = "hidden";
  149. ele.style["margin-left"] = 0;
  150. ele.style["min-width"] = "15rem";
  151. ele.style["max-height"] = "100vh";
  152. }
  153. else {
  154. ele.style["overflow-y"] = "hidden";
  155. ele.style["overflow-x"] = "hidden";
  156. ele.style["margin-left"] = "-15rem";
  157. ele.style["min-width"] = "0";
  158. ele.style["max-height"] = "100vh";
  159. }
  160. }
  161. class CustomToggle extends Control {
  162. constructor(opt) {
  163. opt = opt || {};
  164. let ele = document.createElement("button");
  165. ele.id = 'ui_route_toggle';
  166. ele.innerHTML = "Route";
  167. // class="btn" id="sidebarToggle" style='display:float;'
  168. ele.classList.add("custom-button");
  169. super({
  170. element: ele,
  171. target: opt.target
  172. });
  173. ele.addEventListener('click', this.fire.bind(this), false);
  174. }
  175. fire() {
  176. toggle_sidebar();
  177. // hacky, but functional
  178. //
  179. setTimeout(resize_map, 250);
  180. }
  181. }
  182. // button to toggle side bar route list
  183. //
  184. g_map.addControl(new CustomToggle({ className: 'custom-button' }));
  185. //-------------------
  186. //-------------------
  187. //-------------------
  188. g_map.addControl(new ol.control.Zoom({
  189. className: 'custom-zoom'
  190. }));
  191. const element = document.getElementById('popup');
  192. const popup = new Overlay({
  193. element: element,
  194. positioning: 'bottom-center',
  195. stopEvent: false,
  196. });
  197. g_map.addOverlay(popup);
  198. // display popup on click
  199. //
  200. g_map.on('click', function (evt) {
  201. // not working (wip)
  202. //
  203. /*
  204. const feature = map.forEachFeatureAtPixel(evt.pixel, function (feature) {
  205. return feature;
  206. });
  207. */
  208. });
  209. // change mouse cursor when over marker
  210. g_map.on('pointermove', function (e) {
  211. const pixel = g_map.getEventPixel(e.originalEvent);
  212. const hit = g_map.hasFeatureAtPixel(pixel);
  213. g_map.getTarget().style.cursor = hit ? 'pointer' : '';
  214. });
  215. // Close the popup when the map is moved
  216. //
  217. g_map.on('movestart', function () {
  218. //$(element).popover('dispose');
  219. });
  220. g_map.on("postrender", function(event) {
  221. //console.log("bang...");
  222. });
  223. //-----
  224. function update_buses(json_gtfs) {
  225. let bearingIdxLookup = [ "0", "45", "90", "135", "180", "225", "270", "315" ];
  226. for (let ii=0; ii<json_gtfs.entityList.length; ii++) {
  227. let id = json_gtfs.entityList[ii].id;
  228. let p = json_gtfs.entityList[ii].vehicle.position;
  229. let lat = p.latitude;
  230. let lon = p.longitude;
  231. let bearing = p.bearing;
  232. let lonlat = [ lon , lat ];
  233. let merc = fromLonLat(lonlat);
  234. if (typeof _icon[ii] === "undefined") { continue; }
  235. _icon[ii].feature.getGeometry().setCoordinates(merc);
  236. bearing = (bearing % 360 );
  237. if (bearing < 0) { bearing += 360.0; }
  238. let iheading = Math.floor( (parseInt( bearing ) + 23) / 45 );
  239. if (iheading > 7) { iheading = 0; }
  240. let _nuimg = new Icon({
  241. anchor: [0.5, 46],
  242. anchorXUnits: 'fraction',
  243. anchorYUnits: 'pixels',
  244. src: 'data/bus_gw_' + bearingIdxLookup[iheading] + '.png',
  245. });
  246. _icon[ii].style.setImage(_nuimg);
  247. _icon[ii].style.getImage().setOpacity(1.0);
  248. //console.log(">>", id, lat, lon, bearing);
  249. }
  250. busLayer.getSource().changed();
  251. }
  252. function fetch_gtfs_vehicle_position() {
  253. let xhr = new XMLHttpRequest();
  254. xhr.onload = function() {
  255. let ab = xhr.response;
  256. let json_pb = pb2json.pb2json(ab);
  257. //console.log(">>>", json_pb);
  258. update_buses(json_pb)
  259. };
  260. xhr.responseType = "arraybuffer";
  261. xhr.open("GET", "VehiclePosition.pb");
  262. xhr.send();
  263. }
  264. //--
  265. function ui_show_single_route(route_id) {
  266. if (!(route_id in g_info.ol_route_layer)) {
  267. console.log("route", route_id, "not found in g_info.ol_route_layer");
  268. return;
  269. }
  270. for (let i=0; i<g_info.active_route; i++) {
  271. let route_id = g_info.active_route[i];
  272. let route_layer = g_info.ol_route_layer[route_id];
  273. g_map.removeLayer( route_layer );
  274. }
  275. g_info.active_route = [ route_id ];
  276. g_map.addLayer( g_info.ol_route_layer[route_id] );
  277. }
  278. function init_route_layer() {
  279. for (let route_id in g_info.route_path) {
  280. let route_points = g_info.route_path[route_id];
  281. let merc_points = [];
  282. for (let ii=0; ii<route_points.length; ii++) {
  283. merc_points.push(ol.proj.transform(route_points[ii], 'EPSG:4326', 'EPSG:3857'));
  284. }
  285. let routeLine = new ol.Feature({
  286. geometry: new ol.geom.LineString(merc_points)
  287. });
  288. let routeLineVector = new ol.source.Vector({});
  289. routeLineVector.addFeature(routeLine);
  290. let routeLayer = new VectorLayer({
  291. source: routeLineVector,
  292. style: new ol.style.Style({
  293. fill: new ol.style.Fill({ color: 'rgb(0,0,255,0.25)', weight: 4, opacity: 0.5 }),
  294. stroke: new ol.style.Stroke({ color: 'rgb(0,0,255,0.25)', width: 6, opacity: 0.5 })
  295. }),
  296. zIndex: 0
  297. });
  298. g_info.ol_route_layer[route_id] = routeLayer;
  299. }
  300. }
  301. function init_stop_layer(geojson) {
  302. //DEBUG
  303. return;
  304. if (!geojson) { return; }
  305. let stop_points = geojson.features[0].properties.route_stops;
  306. if (!stop_points) { return; }
  307. let merc_points = [];
  308. for (let ii=0; ii<stop_points.length; ii++) {
  309. let pnt = stop_points[ii].stop.geometry.coordinates;
  310. merc_points.push(ol.proj.transform(pnt, 'EPSG:4326', 'EPSG:3857'));
  311. }
  312. let stopVectorSource = new VectorSource();
  313. for (let ii=0; ii<merc_points.length; ii++) {
  314. let _stop = stop_points[ii].stop;
  315. let _stop_name = _stop.stop_name;
  316. let iconFeature = new Feature({
  317. geometry: new Point([merc_points[ii][0], merc_points[ii][1]]),
  318. name: _stop_name
  319. });
  320. let iconStyle = new Style({
  321. image: new Icon({
  322. anchor: [0.5, 46],
  323. anchorXUnits: 'fraction',
  324. anchorYUnits: 'pixels',
  325. src: 'data/star-3.png',
  326. }),
  327. });
  328. iconStyle.getImage().setOpacity(0.8);
  329. iconFeature.setStyle(iconStyle);
  330. stopVectorSource.addFeature(iconFeature);
  331. }
  332. let stopLayer = new VectorLayer({
  333. source: stopVectorSource,
  334. zIndex: 0
  335. });
  336. g_map.addLayer(stopLayer);
  337. }
  338. function fetch_geojson(url) {
  339. let xhr = new XMLHttpRequest();
  340. xhr.onload = function() {
  341. let geojson = xhr.response;
  342. g_info.geojson = geojson;
  343. //post_process_geojson();
  344. ui_add_route();
  345. init_route_layer(geojson);
  346. init_stop_layer(geojson);
  347. };
  348. xhr.responseType = "json";
  349. xhr.open("GET", url, true);
  350. xhr.send();
  351. }
  352. //---
  353. function post_process_geojson() {
  354. let trip = g_info.geojson.features;
  355. for (let i=0; i<trip.length; i++) {
  356. if (trip[i].type != "trip") { continue; }
  357. let prop = trip[i].properties;
  358. let _route = prop.route;
  359. if (_route.route_id in g_info.route) { continue; }
  360. g_info.route[_route.route_id] = prop.route_stops;
  361. g_info.route_path[_route.route_id] = trip[i].geometry.coordinates;
  362. }
  363. }
  364. // add routes to side menu
  365. //
  366. function ui_add_route() {
  367. let ele = document.getElementById("ui_route_list");
  368. ele.innerHTML = '';
  369. let trip = g_info.geojson.features;
  370. for (let i=0; i<trip.length; i++) {
  371. if (trip[i].type != "trip") { continue; }
  372. let prop = trip[i].properties;
  373. let _route = prop.route;
  374. if (_route.route_id in g_info.route) { continue; }
  375. g_info.route[_route.route_id] = prop.route_stops;
  376. g_info.route_path[_route.route_id] = trip[i].geometry.coordinates;
  377. let _a = document.createElement("a");
  378. _a.classList.add("list-group-item");
  379. _a.classList.add("list-gorup-item-action");
  380. _a.classList.add("list-gorup-item-light");
  381. _a.classList.add("p-3");
  382. _a.innerText = prop.route.route_long_name;
  383. _a.onclick = (function(_r) {
  384. return function() {
  385. ui_show_single_route(_r);
  386. }
  387. })(_route.route_id);
  388. ele.appendChild(_a);
  389. }
  390. toggle_sidebar(g_side_toggle);
  391. }
  392. //---
  393. // resize map
  394. //
  395. function resize_map() {
  396. let h = $(window).height();
  397. let m = document.getElementById("map");
  398. m.style.height = (h-10) + "px";
  399. g_map.updateSize();
  400. }
  401. function init() {
  402. setInterval(fetch_gtfs_vehicle_position, 1000);
  403. setTimeout( function() { fetch_geojson("geojson/test_route.geojson"); }, 100);
  404. $(window).on('resize', resize_map);
  405. }
  406. $(document).ready(function() {
  407. init();
  408. resize_map();
  409. toggle_sidebar(g_side_toggle);
  410. });