1/* 2 All scheduleMap behaviors managed by schedule plugin 3 4 scheduleMapList: Map of 5 mapId => scheduleMap 6 7 scheduleMap 8 mapId: div map ID 9 map: map 10 poi: Point Of Interest 11 poiSize: # of number 12 clusterLayer: clusterLayer 13*/ 14var scheduleMapList = {}; 15 16var scheduleUseMap = JSINFO['schedule']['useMap']; 17var scheduleZoom = JSINFO['schedule']['defaultZoom']; 18var scheduleCenter = JSINFO['schedule']['defaultCenter']; 19var schedulePoiUri = DOKU_BASE+'lib/plugins/schedule/images/poi.png'; 20var scheduleSelectedPoiUri = DOKU_BASE+'lib/plugins/schedule/images/poiRed.png'; 21var scheduleEmptyUri = DOKU_BASE+'lib/plugins/schedule/images/empty.png'; 22var scheduleIconCalendarUri = DOKU_BASE+'lib/plugins/schedule/images/calendar.png'; 23var scheduleAjaxPoiUri = DOKU_BASE+"lib/plugins/schedule/ajaxPOI.php"; 24var schedulePrecision = 5; 25 26var locationFormatRE = new RegExp ("^\\s*\\(\\s*([\\-0-9.]+)\\s*\\|\\s*([\\-0-9.]+)\\s*\\)\\s*(.*)$"); 27 28var iconStyle; 29var iconSelectedStyle; 30 31/* selected LI on city UL */ 32var scheduleSelectedCity = null; 33 34// ======================================== 35/* format location */ 36function scheduleGetLocation (lat, lon) { 37 return "("+ 38 parseFloat (parseFloat (lat).toFixed (schedulePrecision))+"|"+ 39 parseFloat (parseFloat (lon).toFixed (schedulePrecision))+")"; 40} 41 42// ======================================== 43/* convert and reverse location */ 44function scheduleGetCoordFromLocation (latLonArray) { 45 return ol.proj.transform ([latLonArray [1], latLonArray [0]], 'EPSG:4326', 'EPSG:3857'); 46} 47 48// ======================================== 49/* convert and reverse location */ 50function scheduleGetLocationFromCoord (coord) { 51 var lonLat = ol.proj.transform (coord, 'EPSG:3857', 'EPSG:4326'); 52 return [parseFloat (lonLat[1]).toFixed (schedulePrecision), parseFloat (lonLat [0]).toFixed (schedulePrecision)]; 53} 54 55// ======================================== 56/* center scheduleMap according POI markers */ 57function scheduleCenterMap (scheduleMap) { 58 if (!scheduleUseMap) 59 return; 60 if (scheduleMap.poiSize) { 61 scheduleMap.map.getView ().fit (scheduleMap.poi.getExtent (), scheduleMap.map.getSize ()); 62 scheduleMap.map.getView ().setZoom (Math.min (scheduleMap.map.getView ().getZoom ()*.9, 63 scheduleZoom)); 64 return; 65 } 66 scheduleMap.map.getView ().setCenter (scheduleGetCoordFromLocation (scheduleCenter)); 67 scheduleMap.map.getView ().setZoom (6); //scheduleZoom); 68} 69 70// ======================================== 71/* remove all POI markers */ 72function scheduleClearMarkers (scheduleMap) { 73 if (!scheduleUseMap) 74 return; 75 scheduleMap.poiSize = 0; 76 if (scheduleMap.poi !== undefined) 77 scheduleMap.poi.clear (); 78} 79 80// ======================================== 81/* add a wellknown city marker */ 82function scheduleAddCityMarker (scheduleMap, cityCode) { 83 if (! cityCode) 84 return; 85 var inseeLocation = inseeCityNameLatLon[cityCode]; 86 if (!style) { 87 // XXX not found 88 return; 89 } 90 scheduleAddLatLonCityMarker (scheduleMap, cityCode, 91 inseeCityNameLatLon[cityCode][1], 92 inseeCityNameLatLon[cityCode][2]); 93} 94 95// ======================================== 96/* add a positioned city marker */ 97function scheduleAddLatLonCityMarker (scheduleMap, cityCode, lat, lon) { 98 if (!scheduleUseMap) 99 return; 100 scheduleMap.poiSize++; 101 if (scheduleMap.poi === undefined) 102 return; 103 scheduleMap.poi.addFeature (new ol.Feature({ 104 geometry: new ol.geom.Point (scheduleGetCoordFromLocation ([lat, lon])), 105 cityCode: cityCode, 106 location: scheduleGetLocation (lat, lon), 107 lat: lat, 108 lon: lon, 109 selected: false 110 })); 111} 112 113// ======================================== 114/* highlight all cities on the list of adress */ 115function scheduleHighlightAddress (locations) { 116 if (!locations) { 117 jQuery ('div.scheduleAddresse').removeClass("poiAdrLight"); 118 return; 119 } 120 jQuery ('div.scheduleAddresse').each (function () { 121 var adrBlock = jQuery (this); 122 var location = adrBlock.attr ('location'); 123 if (location && (','+locations).indexOf (location) >= 0) 124 adrBlock.addClass("poiAdrLight"); 125 else 126 adrBlock.removeClass("poiAdrLight"); 127 }); 128} 129 130function scheduleHighlightLocation (location) { 131 scheduleHighlightAddress (location); 132 scheduleHighlightPOI (location); 133} 134 135// ======================================== 136/* display cities from a UL */ 137function scheduleAddAllCityFromUl (scheduleMap, ul) { 138 scheduleClearMarkers (scheduleMap); 139 ul.find ('li').each (function (index) { 140 var li = jQuery (this); 141 scheduleAddLatLonCityMarker (scheduleMap, li.attr ('insee'), 142 li.attr ('lat'), li.attr ('lon')); 143 }); 144 if (!scheduleUseMap) 145 return; 146 if (scheduleMap.poi !== undefined){ 147 scheduleMap.poi.changed (); 148 scheduleMap.map.render (); 149 } 150} 151 152// ======================================== 153function scheduleAddCityToUl (ul, cityName) { 154 var cityCode = /[0-9AB]{5,}/.exec (cityName); 155 var lat = ""; 156 var lon = ""; 157 if (cityCode != null) 158 cityCode = cityCode[0]; 159 if (!cityCode) 160 cityCode = inseeCityNameInsee [cityName.toLowerCase ()]; 161 if (!cityCode) 162 cityName = cityCode = cityName.replace(/[^A-ZÀÂÆÇÉÈÊËÎÏÑÔŒÙÛÜÿ a-zàâæçéèêëîïñôœùûüÿ'\-]/gi,''); 163 else { 164 cityName = inseeCityNameLatLon[cityCode][0]; 165 lat = inseeCityNameLatLon[cityCode][1]; 166 lon = inseeCityNameLatLon[cityCode][2]; 167 } 168 var recorded = false; 169 ul.find ('li[insee="'+cityCode+'"][lat="'+lat+'"][lon="'+lon+'"]').each (function (index) { 170 recorded = true; 171 scheduleSetSelectCity (jQuery (this)); 172 }); 173 if (recorded) 174 return; 175 var lonLat = 176 (lat && lon) ? 177 ' lat="'+lat+'" lon="'+lon+'"' : 178 ' class="unknown"'; 179 ul.append ('<li insee="'+cityCode+'"'+lonLat+' onclick="scheduleSelectCity (this)">'+ 180 '<img class="checked" src="'+scheduleEmptyUri+'" width="10" height="16" onclick="scheduleRemoveCity (this)"/>'+ 181 ' <span class="addresse"></span> '+ 182 '<span class="city">'+cityName+'</span></li>'); 183 scheduleSetSelectCity (ul.find ("li").last ()); 184} 185 186// ======================================== 187/* Sort UL by city name (XXX what about addresse ?) */ 188function scheduleSortUlCities (ul) { 189 ul.children ('li').sort (function (a, b) { 190 var upa = jQuery (a).find ('span.city').text ().toUpperCase (); 191 var upb = jQuery (b).find ('span.city').text ().toUpperCase (); 192 return (upa < upb) ? -1 : (upa > upb) ? 1 : 0; 193 }).appendTo (ul[0]); 194} 195 196function scheduleAjaxPOILine (action) { 197 if (!scheduleSelectedCity) 198 // XXX dire quelque chose 199 return; 200 var insee = scheduleSelectedCity.attr ('insee'); 201 var lat = scheduleSelectedCity.attr ('lat'); 202 var lon = scheduleSelectedCity.attr ('lon'); 203 var addr = scheduleSelectedCity.find ('span.addresse').html ().replace (/<br>/gi, '~br~'); 204 line = new Array (); 205 line.push (insee); 206 line.push (lat); 207 line.push (lon); 208 line.push (addr); 209 jQuery.ajax ({ 210 type: "POST", 211 url: scheduleAjaxPoiUri, 212 cache: true, 213 async: true, 214 data: { action: action, line: line.join ('|') }, 215 success: function (response) { alert (response); } 216 }); 217} 218 219function scheduleAddInsee () { 220 scheduleAjaxPOILine ("add"); 221} 222function scheduleRemoveInsee () { 223 scheduleAjaxPOILine ("remove"); 224} 225function scheduleTestInsee () { 226} 227 228function scheduleSetSelectCity (li) { 229 scheduleSelectedCity = li; 230 // change autocomplete 231 var cityCode = scheduleSelectedCity.attr ('insee'); 232 var form = scheduleSelectedCity.closest ('.scheduleCitiesForm'); 233 var input = form.find ('input[name="addr"]'); 234 input. val (scheduleSelectedCity.find ('span.addresse').html ().replace (/<br>/gi, '~br~')); 235 jQuery.ajax ({ 236 type: "POST", 237 url: scheduleAjaxPoiUri, 238 cache: true, 239 async: true, 240 data: { action: "list", insee: cityCode }, 241 success: function (response) { 242 var db = jQuery.parseJSON (response); 243 source = new Array (); 244 for (var i = 0; i < db.length; i++) { 245 var vals = db[i].split ("|"); 246 source.push ("("+vals[0]+"|"+vals[1]+") "+vals[2]); 247 } 248 input.autocomplete ({ source: source }); 249 } 250 }); 251} 252 253function scheduleFocusCity () { 254 if (!scheduleUseMap) 255 return; 256 var form = scheduleSelectedCity.closest ('.scheduleCitiesForm'); 257 // focus on map 258 form.find ('.scheduleMap').each (function () { 259 var scheduleMap = scheduleMapList [jQuery (this).attr ('id')]; 260 scheduleMap.map.getView ().setCenter (scheduleGetCoordFromLocation ([scheduleSelectedCity.attr ('lat'), scheduleSelectedCity.attr ('lon')])); 261 scheduleMap.map.getView ().setZoom (Math.min (scheduleMap.map.getView ().getZoom (), scheduleZoom)); 262 }); 263} 264 265// ======================================== 266/* User LI selection and focus on map */ 267function scheduleSelectCity (item) { 268 scheduleSetSelectCity (jQuery (item).closest ('li')); 269 scheduleFocusCity (); 270} 271 272// ======================================== 273/* User remove LI */ 274function scheduleRemoveCity (item) { 275 var li = jQuery (item).closest ('li'); 276 var ul = li.closest ('ul'); 277 li.remove (); 278 scheduleUpdateUlCity (ul); 279} 280 281function scheduleUpdateUlCity (ul) { 282 ul.closest ('.scheduleCitiesForm'). find ('.scheduleMap').each (function () { 283 var scheduleMap = scheduleMapList [jQuery (this).attr ('id')]; 284 scheduleAddAllCityFromUl (scheduleMap, ul); 285 scheduleCenterMap (scheduleMap); 286 }); 287 scheduleCheckInputs (); 288} 289 290// ======================================== 291/* Find all initial values */ 292function scheduleInitPOI () { 293 jQuery ('.schedulePOI').each (function () { 294 var poiDiv = jQuery (this); 295 var scheduleMap = scheduleMapList [poiDiv.find ('.scheduleMap').attr ('id')]; 296 scheduleClearMarkers (scheduleMap); 297 poiDiv.find ('span.poiLocations').each (function () { 298 var poiLocations = jQuery (this).text ().split (','); 299 for (var i = 0; i < poiLocations.length; i++) { 300 var latLon = poiLocations[i]; 301 var match = latLon.match (locationFormatRE); 302 if (match) { 303 var lat = parseFloat (match [1]).toFixed (schedulePrecision); 304 var lon = parseFloat (match [2]).toFixed (schedulePrecision); 305 scheduleAddLatLonCityMarker (scheduleMap, "", lat, lon); 306 } else 307 // cityCode = latLon 308 scheduleAddCityMarker (scheduleMap, latLon); 309 } 310 scheduleCenterMap (scheduleMap); 311 }); 312 poiDiv.find ('ul.poiLatLon').each (function () { 313 jQuery (this).find ('li').each (function (index) { 314 var li = jQuery (this); 315 scheduleAddLatLonCityMarker (scheduleMap, li.text (), 316 li.attr ('lat'), li.attr ('lon')); 317 }); 318 scheduleCenterMap (scheduleMap); 319 }); 320 }); 321 jQuery ('.scheduleCitiesForm').each (function () { 322 var scheduleCitiesForm = jQuery (this); 323 var scheduleMapDiv = scheduleCitiesForm.find ('.scheduleMap'); 324 if (scheduleMapDiv[0] === undefined) 325 return; 326 var scheduleMap = scheduleMapList [scheduleMapDiv.attr ('id')]; 327 scheduleAddAllCityFromUl (scheduleMap, jQuery (this).find ('.cities')); 328 scheduleCenterMap (scheduleMap); 329 }); 330} 331 332// ======================================== 333/* Find all maps */ 334function scheduleInitMaps () { 335 if (!scheduleUseMap) 336 return; 337 338 jQuery ('.scheduleMap').each (function () { 339 var mapDiv = jQuery (this).find ('div'); 340 if (mapDiv !== undefined && mapDiv && mapDiv.length) 341 return; 342 var mapId = jQuery (this).attr ('id'); 343 var osm = new ol.layer.Tile ({ 344 source: new ol.source.OSM () 345 }); 346 var poi = new ol.source.Vector ({ 347 features: [] 348 }); 349 iconStyle = new ol.style.Style ({ 350 image: new ol.style.Icon (/** @type {olx.style.IconOptions} */ ({ 351 anchor: [.5, 1], 352 anchorXUnits: 'fraction', 353 anchorYUnits: 'fraction', 354 opacity: 0.75, 355 src: schedulePoiUri 356 })) 357 }); 358 iconSelectedStyle = new ol.style.Style ({ 359 image: new ol.style.Icon (/** @type {olx.style.IconOptions} */ ({ 360 anchor: [.5, 1], 361 anchorXUnits: 'fraction', 362 anchorYUnits: 'fraction', 363 opacity: 0.75, 364 src: scheduleSelectedPoiUri 365 })) 366 }); 367 var clusters = new ol.source.Cluster({ 368 distance: 10, 369 source: poi, 370 }); 371 var styleCache = {false: {}, true: {}}; 372 var clusterLayer = new ol.layer.Vector({ 373 source: clusters, 374 style: function (feature) { 375 var features = feature.get ('features'); 376 var size = features.length; 377 if (size < 2) 378 return features [0].get ('selected') ? iconSelectedStyle : iconStyle; 379 var selected = false; 380 features.forEach (function (item) { 381 if (item.get ('selected')) 382 selected = true; 383 }); 384 385 var style = styleCache[selected][size]; 386 if (!style) { 387 style = [new ol.style.Style({ 388 image: new ol.style.Icon (/** @type {olx.style.IconOptions} */ ({ 389 anchor: [.5, 1], 390 anchorXUnits: 'fraction', 391 anchorYUnits: 'fraction', 392 opacity: 0.75, 393 src: selected ? scheduleSelectedPoiUri : schedulePoiUri 394 })), 395 text: new ol.style.Text({ 396 text: size.toString (), 397 offsetY: -24 // XXX textpos conf ? 398 }) 399 })]; 400 styleCache[selected][size] = style; 401 } 402 return style; 403 } 404 }); 405 var map = new ol.Map ({ 406 target: mapId, 407 layers: [osm, clusterLayer], 408 //logo: false, 409 controls: ol.control.defaults ({ 410 zoom: true, 411 attribution: false, 412 rotate: false 413 }), 414 view: new ol.View ({ 415 center: ol.proj.fromLonLat (scheduleCenter), 416 zoom: scheduleZoom 417 }) 418 }); 419 if (jQuery (this).hasClass ("scheduleMapDisplay")) 420 map.set ("mapType", "display"); 421 if (jQuery (this).hasClass ("scheduleMapForm")) 422 map.set ("mapType", "form"); 423 if (jQuery (this).hasClass ("scheduleMapCalendar")) 424 map.set ("mapType", "calendar"); 425 426 map.on ("singleclick", function (evt) { 427 var f; 428 map.forEachFeatureAtPixel (evt.pixel, function (feature) { 429 f = feature; 430 return true; 431 }); 432 switch (map.get ("mapType")) { 433 case "display": 434 if (f) { 435 if (f.get ('features').length) 436 f = f.get ('features')[0]; 437 window.open ("https://www.openstreetmap.org/?mlat="+f.get ('lat')+"&mlon="+f.get ('lon')+"&zoom="+scheduleZoom, "_self"); 438 } 439 break; 440 case "form": 441 var location = scheduleGetLocationFromCoord (evt.coordinate); 442 scheduleSelectedCity.attr ('lat', location[0]); 443 scheduleSelectedCity.attr ('lon', location[1]); 444 scheduleAddAllCityFromUl (scheduleMap, scheduleSelectedCity.closest ('ul')); 445 break; 446 } 447 }); 448 map.on ("pointermove", function (evt) { 449 var locations = new Set (); 450 map.forEachFeatureAtPixel (evt.pixel, function (feature) { 451 feature.get ('features').forEach (function (item) { 452 locations.add (item.get ('location')); 453 }); 454 }); 455 locations = Array.from (locations); 456 scheduleHighlightDays (locations); 457 scheduleHighlightLocation (locations.join (',')); 458 }); 459 var scheduleMap = { 460 mapId: mapId, 461 map: map, 462 poiSize: 0, 463 poi: poi, 464 clusterLayer: clusterLayer 465 }; 466 467 scheduleMapList[mapId] = scheduleMap; 468 scheduleCenterMap (scheduleMap); 469 }); 470} 471 472// ======================================== 473/* Initialisation and attach function */ 474jQuery (function () { 475 jQuery (function () { 476 // create tabs before OpenLayers stuff (important !) 477 jQuery (".scheduleTabForm").tabs ({ 478 active: 0 479 }); 480 jQuery ("#scheduleHelp").accordion ({ 481 collapsible: true, 482 animated: false, 483 active: false 484 }); 485 jQuery.datepicker.setDefaults ({ 486 showOn: "both", 487 buttonImageOnly: true, 488 buttonImage: scheduleIconCalendarUri, 489 buttonText: "", 490 firstDay: 0 491 }); 492 jQuery.datepicker.formatDate ("yy-mm-dd"); 493 jQuery (".scheduleTabForm .date").datepicker (); 494 495 divError = jQuery ("div.schedule").prev ("div.error"); 496 if (divError !== undefined && divError && divError.length) 497 // XXX a tester (avant divError.size ()) 498 scheduleForceCheckInputs (); 499 }); 500 501 // check and format form request 502 jQuery ('.scheduleFinalForm').submit (function () { 503 var scheduleFinalForm = jQuery (this); 504 if (!scheduleForceCheckInputs ()) 505 return false; 506 var scheduleForm = scheduleFinalForm.closest ('.scheduleTabForm'); 507 var scheduleCitiesForm = scheduleForm.find ('.scheduleCitiesForm'); 508 var cities = new Array (); 509 var lats = new Array (); 510 var lons = new Array (); 511 var addrs = new Array (); 512 scheduleCitiesForm.find ('li').each (function (index) { 513 var li = jQuery (this); 514 cities.push (li.attr ('insee')); 515 lats.push (li.attr ('lat')); 516 lons.push (li.attr ('lon')); 517 addrs.push (li.find ('span.addresse').html ()); 518 }); 519 scheduleFinalForm.append ('<input type="hidden" name="schd[where]" value="'+cities.join (",")+'"/>'); 520 scheduleFinalForm.append ('<input type="hidden" name="schd[lat]" value="'+lats.join (",")+'"/>'); 521 scheduleFinalForm.append ('<input type="hidden" name="schd[lon]" value="'+lons.join (",")+'"/>'); 522 scheduleFinalForm.append ('<input type="hidden" name="schd[addr]" value="'+addrs.join ("|").replace (/<br>/gi, '~br~')+'"/>'); 523 524 var scheduleMiscForm = scheduleForm.find ('.scheduleMiscForm'); 525 scheduleMiscForm.find ('input[type="text"]').each (function (index) { 526 if (this.value) 527 scheduleFinalForm.append ('<input type="hidden" name="'+this.name+'" value="'+this.value.replace(/"/gi, "''")+'"/>'); 528 }); 529 scheduleMiscForm.find ('select:not([class="members"])').each (function (index) { 530 scheduleFinalForm.append ('<input type="hidden" name="'+this.name+'" value="'+this.value+'"/>'); 531 }); 532 var members = new Array (); 533 scheduleMiscForm.find ('select[class="members"]').each (function (index) { 534 jQuery (this).find ('option:selected').each (function (index) { 535 members.push (jQuery (this).val ()); 536 }); 537 }); 538 if (members.length > 0) 539 scheduleFinalForm.append ('<input type="hidden" name="schd[member]" value="'+members.join (",")+'"/>'); 540 scheduleMiscForm.find ('textarea').each (function (index) { 541 var val = jQuery (this).val ().replace(/"/gi, "''"); 542 var name = jQuery (this).attr ('name'); 543 scheduleFinalForm.append ('<input type="hidden" name="'+name+'" value="'+val+'"/>'); 544 }); 545 return true; 546 }); 547 548 // city validation 549 jQuery ('.scheduleCitiesForm input[name="city"]').keypress (function (e) { 550 if (e.which != 13) 551 return; 552 var input = jQuery (this); 553 var form = input.closest ('.scheduleCitiesForm'); 554 var cityName = input.val (); 555 if (!cityName) 556 return false; 557 input.val (""); 558 form.find ('input[name="addr"]').val (""); 559 var ul = form.find ('ul'); 560 scheduleAddCityToUl (ul, cityName); 561 scheduleSortUlCities (ul); 562 scheduleUpdateUlCity (ul); 563 return false; 564 }); 565 566 // full adress validation 567 jQuery ('.scheduleCitiesForm input[name="addr"]').keypress (function (e) { 568 if (e.which != 13) 569 return; 570 if (!scheduleSelectedCity) 571 return; 572 var input = jQuery (this); 573 var form = input.closest ('.scheduleCitiesForm'); 574 var addr = input.val (); 575 var match = addr.match (locationFormatRE); 576 if (match) { 577 addr = match [3]; 578 scheduleSelectedCity.attr ('lat', parseFloat (match [1]).toFixed (schedulePrecision)); 579 scheduleSelectedCity.attr ('lon', parseFloat (match [2]).toFixed (schedulePrecision)); 580 var scheduleMap = scheduleMapList [form.find ('.scheduleMap').attr ('id')]; 581 scheduleAddAllCityFromUl (scheduleMap, scheduleSelectedCity.closest ('ul')); 582 scheduleFocusCity (); 583 } 584 input.val (addr); 585 scheduleSelectedCity.find ('span.addresse').html (addr.replace (/~br~/gi, '<br/>')); 586 }); 587 588 // default form validation for each text field 589 jQuery ('.scheduleMiscForm input').keypress (function (e) { 590 if (e.which != 13) 591 return; 592 jQuery (this).closest ('.scheduleTabForm').find ('input[type="submit"]').first ().trigger ('click'); 593 }); 594 595 // init maps 596 scheduleInitMaps (); 597 scheduleInitPOI (); 598}); 599 600// ======================================== 601