Commit 2933246f authored by Eric - kg6wxc's avatar Eric - kg6wxc
Browse files

Merge branch 'node_search' into 'master'

enhancement/bugfix: Search for and zoom to node

Closes #11

See merge request !15
parents d0705e13 78b26614
...@@ -963,6 +963,13 @@ function create_MapLayers($numNodes, $numLinks, $numMarkers) ...@@ -963,6 +963,13 @@ function create_MapLayers($numNodes, $numLinks, $numMarkers)
var fiveGHzStations = new L.LayerGroup(); var fiveGHzStations = new L.LayerGroup();
var otherStations = new L.LayerGroup(); var otherStations = new L.LayerGroup();
var upgradeStations = new L.LayerGroup();\n var upgradeStations = new L.LayerGroup();\n
var allStations = L.layerGroup([
nineHundredMHzStations,
twoGHzStations,
threeGHzStations,
fiveGHzStations,
otherStations]);
"; ";
if ($numLinks > 0) if ($numLinks > 0)
...@@ -1227,15 +1234,15 @@ map.fullscreenControl.setPosition('verticalcenterleft'); ...@@ -1227,15 +1234,15 @@ map.fullscreenControl.setPosition('verticalcenterleft');
EOD; EOD;
$Content .= <<< EOD $Content .= <<< EOD
//the ruler
var rulerOptions = {position: 'verticalcenterleft', //the search button
lengthUnit: { L.control.search({
factor: 0.621371, layer: allStations,
display: 'Miles', initial: false,
decimal: 3} zoom: 16,
}; position: 'verticalcenterleft',
L.control.ruler(rulerOptions).addTo(map); hideMarkerOnCollapse: true
document.getElementById('ruler').title = 'Ruler\\n(ESC 1x to stop, 2x to remove)'; }).addTo(map);
//the watermark logo //the watermark logo
L.Control.Watermark = L.Control.extend({ L.Control.Watermark = L.Control.extend({
...@@ -1268,12 +1275,7 @@ L.control.watermark({position: 'bottomright'}).addTo(map); ...@@ -1268,12 +1275,7 @@ L.control.watermark({position: 'bottomright'}).addTo(map);
*/ */
legend.addTo(map); legend.addTo(map);
legendHidden.addTo(map); legendHidden.addTo(map);
/*
* Layer Control
*/
var layerControls = L.control.groupedLayers(baseLayers, groupedOverlays, {position: 'verticalcenterleft'}).addTo(map);
L.DomEvent.disableClickPropagation(layerControls._container);
L.DomEvent.disableScrollPropagation(layerControls._container);
/* /*
* Scale Control * Scale Control
*/ */
...@@ -1337,6 +1339,24 @@ EOD; ...@@ -1337,6 +1339,24 @@ EOD;
/* Attribution */ /* Attribution */
//attributionCtrl({position: 'bottomleft'}).addTo(map);\n //attributionCtrl({position: 'bottomleft'}).addTo(map);\n
//the left side controls go in the order they are added.
//we want the ruler and the layers control to be the last two on the bottom
$Content .= <<< EOD
//the ruler
var rulerOptions = {position: 'verticalcenterleft',
lengthUnit: {
factor: 0.621371,
display: 'Miles',
decimal: 3}
};
L.control.ruler(rulerOptions).addTo(map);
document.getElementById('ruler').title = 'Ruler\\n(ESC 1x to stop, 2x to remove)';
//Grouped Layer Control
var layerControls = L.control.groupedLayers(baseLayers, groupedOverlays, {position: 'verticalcenterleft'}).addTo(map);
L.DomEvent.disableClickPropagation(layerControls._container);
L.DomEvent.disableScrollPropagation(layerControls._container);
EOD;
return $Content; return $Content;
} }
?> ?>
......
.leaflet-container .leaflet-control-search {
position:relative;
float:left;
background:#fff;
color:#1978cf;
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
border-radius: 4px;
background-color: rgba(255, 255, 255, 0.8);
z-index:1000;
margin-left: 10px;
margin-top: 10px;
}
.leaflet-control-search.search-exp {/*expanded*/
background: #fff;
border: 2px solid rgba(0,0,0,0.2);
background-clip: padding-box;
}
.leaflet-control-search .search-input {
display:block;
float:left;
background: #fff;
border:1px solid #666;
border-radius:2px;
height:22px;
padding:0 20px 0 2px;
margin:4px 0 4px 4px;
}
.leaflet-control-search.search-load .search-input {
background: url('../images/loader.gif') no-repeat center right #fff;
}
.leaflet-control-search.search-load .search-cancel {
visibility:hidden;
}
.leaflet-control-search .search-cancel {
display:block;
width:22px;
height:22px;
position:absolute;
right:28px;
margin:6px 0;
background: url('../images/search-icon.png') no-repeat 0 -46px;
text-decoration:none;
filter: alpha(opacity=80);
opacity: 0.8;
}
.leaflet-control-search .search-cancel:hover {
filter: alpha(opacity=100);
opacity: 1;
}
.leaflet-control-search .search-cancel span {
display:none;/* comment for cancel button imageless */
font-size:18px;
line-height:20px;
color:#ccc;
font-weight:bold;
}
.leaflet-control-search .search-cancel:hover span {
color:#aaa;
}
.leaflet-control-search .search-button {
display:block;
float:left;
width:30px;
height:30px;
background: url('../images/search-icon.png') no-repeat 4px 4px #fff;
border-radius:4px;
}
.leaflet-control-search .search-button:hover {
background: url('../images/search-icon.png') no-repeat 4px -20px #fafafa;
}
.leaflet-control-search .search-tooltip {
position:absolute;
top:100%;
left:0;
float:left;
list-style: none;
padding-left: 0;
min-width:120px;
max-height:122px;
box-shadow: 1px 1px 6px rgba(0,0,0,0.4);
background-color: rgba(0, 0, 0, 0.25);
z-index:1010;
overflow-y:auto;
overflow-x:hidden;
cursor: pointer;
}
.leaflet-control-search .search-tip {
margin:2px;
padding:2px 4px;
display:block;
color:black;
background: #eee;
border-radius:.25em;
text-decoration:none;
white-space:nowrap;
vertical-align:center;
}
.leaflet-control-search .search-button:hover {
background-color: #f4f4f4;
}
.leaflet-control-search .search-tip-select,
.leaflet-control-search .search-tip:hover {
background-color: #fff;
}
.leaflet-control-search .search-alert {
cursor:pointer;
clear:both;
font-size:.75em;
margin-bottom:5px;
padding:0 .25em;
color:#e00;
font-weight:bold;
border-radius:.25em;
}
.leaflet-control-layers-group-name {
font-weight: bold;
margin-bottom: .2em;
margin-left: 3px;
}
.leaflet-control-layers-group {
margin-bottom: .5em;
}
.leaflet-control-layers-scrollbar {
overflow-y: scroll;
padding-right: 10px;
}
.leaflet-control-layers-group-name{font-weight:700;margin-bottom:.2em;margin-left:3px}.leaflet-control-layers-group{margin-bottom:.5em}.leaflet-control-layers-scrollbar{overflow-y:scroll;padding-right:10px}
\ No newline at end of file
...@@ -208,7 +208,8 @@ MAP_DETAILS { ...@@ -208,7 +208,8 @@ MAP_DETAILS {
display: inline; display: inline;
} }
.leaflet-control-layers-expanded, .leaflet-control-layers-list { .leaflet-control-layers-expanded, .leaflet-control-layers-list {
height: 200px !important; height: 200px !important;
overflow: auto !important;
} }
/** New stuff for the popup tabs **/ /** New stuff for the popup tabs **/
......
/*
* Leaflet Control Search v2.9.7 - 2019-01-14
*
* Copyright 2019 Stefano Cudini
* stefano.cudini@gmail.com
* http://labs.easyblog.it/
*
* Licensed under the MIT license.
*
* Demo:
* http://labs.easyblog.it/maps/leaflet-search/
*
* Source:
* git@github.com:stefanocudini/leaflet-search.git
*
*/
/*
Name Data passed Description
Managed Events:
search:locationfound {latlng, title, layer} fired after moved and show markerLocation
search:expanded {} fired after control was expanded
search:collapsed {} fired after control was collapsed
search:cancel {} fired after cancel button clicked
Public methods:
setLayer() L.LayerGroup() set layer search at runtime
showAlert() 'Text message' show alert message
searchText() 'Text searched' search text by external code
*/
//TODO implement can do research on multiple sources layers and remote
//TODO history: false, //show latest searches in tooltip
//FIXME option condition problem {autoCollapse: true, markerLocation: true} not show location
//FIXME option condition problem {autoCollapse: false }
//
//TODO here insert function search inputText FIRST in _recordsCache keys and if not find results..
// run one of callbacks search(sourceData,jsonpUrl or options.layer) and run this.showTooltip
//
//TODO change structure of _recordsCache
// like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...}
// in this mode every record can have a free structure of attributes, only 'loc' is required
//TODO important optimization!!! always append data in this._recordsCache
// now _recordsCache content is emptied and replaced with new data founded
// always appending data on _recordsCache give the possibility of caching ajax, jsonp and layersearch!
//
//TODO here insert function search inputText FIRST in _recordsCache keys and if not find results..
// run one of callbacks search(sourceData,jsonpUrl or options.layer) and run this.showTooltip
//
//TODO change structure of _recordsCache
// like this: _recordsCache = {"text-key1": {loc:[lat,lng], ..other attributes.. }, {"text-key2": {loc:[lat,lng]}...}, ...}
// in this way every record can have a free structure of attributes, only 'loc' is required
(function (factory) {
if(typeof define === 'function' && define.amd) {
//AMD
define(['leaflet'], factory);
} else if(typeof module !== 'undefined') {
// Node/CommonJS
module.exports = factory(require('leaflet'));
} else {
// Browser globals
if(typeof window.L === 'undefined')
throw 'Leaflet must be loaded first';
factory(window.L);
}
})(function (L) {
L.Control.Search = L.Control.extend({
includes: L.version[0]==='1' ? L.Evented.prototype : L.Mixin.Events,
options: {
url: '', //url for search by ajax request, ex: "search.php?q={s}". Can be function to returns string for dynamic parameter setting
layer: null, //layer where search markers(is a L.LayerGroup)
sourceData: null, //function to fill _recordsCache, passed searching text by first param and callback in second
//TODO implements uniq option 'sourceData' to recognizes source type: url,array,callback or layer
jsonpParam: null, //jsonp param name for search by jsonp service, ex: "callback"
propertyLoc: 'loc', //field for remapping location, using array: ['latname','lonname'] for select double fields(ex. ['lat','lon'] ) support dotted format: 'prop.subprop.title'
propertyName: 'title', //property in marker.options(or feature.properties for vector layer) trough filter elements in layer,
formatData: null, //callback for reformat all data from source to indexed data object
filterData: null, //callback for filtering data from text searched, params: textSearch, allRecords
moveToLocation: null, //callback run on location found, params: latlng, title, map
buildTip: null, //function to return row tip html node(or html string), receive text tooltip in first param
container: '', //container id to insert Search Control
zoom: null, //default zoom level for move to location
minLength: 1, //minimal text length for autocomplete
initial: true, //search elements only by initial text
casesensitive: false, //search elements in case sensitive text
autoType: true, //complete input with first suggested result and select this filled-in text.
delayType: 400, //delay while typing for show tooltip
tooltipLimit: -1, //limit max results to show in tooltip. -1 for no limit, 0 for no results
tipAutoSubmit: true, //auto map panTo when click on tooltip
firstTipSubmit: false, //auto select first result con enter click
autoResize: true, //autoresize on input change
collapsed: true, //collapse search control at startup
autoCollapse: false, //collapse search control after submit(on button or on tips if enabled tipAutoSubmit)
autoCollapseTime: 1200, //delay for autoclosing alert and collapse after blur
textErr: 'Location not found', //error message
textCancel: 'Cancel', //title in cancel button
textPlaceholder: 'Search...', //placeholder value
hideMarkerOnCollapse: false, //remove circle and marker on search control collapsed
position: 'topleft',
marker: { //custom L.Marker or false for hide
icon: false, //custom L.Icon for maker location or false for hide
animate: true, //animate a circle over location found
circle: { //draw a circle in location found
radius: 10,
weight: 3,
color: '#e03',
stroke: true,
fill: false
}
}
},
_getPath: function(obj, prop) {
var parts = prop.split('.'),
last = parts.pop(),
len = parts.length,
cur = parts[0],
i = 1;
if(len > 0)
while((obj = obj[cur]) && i < len)
cur = parts[i++];
if(obj)
return obj[last];
},
_isObject: function(obj) {
return Object.prototype.toString.call(obj) === "[object Object]";
},
initialize: function(options) {
L.Util.setOptions(this, options || {});
this._inputMinSize = this.options.textPlaceholder ? this.options.textPlaceholder.length : 10;
this._layer = this.options.layer || new L.LayerGroup();
this._filterData = this.options.filterData || this._defaultFilterData;
this._formatData = this.options.formatData || this._defaultFormatData;
this._moveToLocation = this.options.moveToLocation || this._defaultMoveToLocation;
this._autoTypeTmp = this.options.autoType; //useful for disable autoType temporarily in delete/backspace keydown
this._countertips = 0; //number of tips items
this._recordsCache = {}; //key,value table! to store locations! format: key,latlng
this._curReq = null;
},
onAdd: function (map) {
this._map = map;
this._container = L.DomUtil.create('div', 'leaflet-control-search');
this._input = this._createInput(this.options.textPlaceholder, 'search-input');
this._tooltip = this._createTooltip('search-tooltip');
this._cancel = this._createCancel(this.options.textCancel, 'search-cancel');
this._button = this._createButton(this.options.textPlaceholder, 'search-button');
this._alert = this._createAlert('search-alert');
if(this.options.collapsed===false)
this.expand(this.options.collapsed);
if(this.options.marker) {
if(this.options.marker instanceof L.Marker || this.options.marker instanceof L.CircleMarker)
this._markerSearch = this.options.marker;
else if(this._isObject(this.options.marker))
this._markerSearch = new L.Control.Search.Marker([0,0], this.options.marker);
this._markerSearch._isMarkerSearch = true;
}
this.setLayer( this._layer );
map.on({
// 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove
'resize': this._handleAutoresize
}, this);
return this._container;
},
addTo: function (map) {
if(this.options.container) {
this._container = this.onAdd(map);
this._wrapper = L.DomUtil.get(this.options.container);
this._wrapper.style.position = 'relative';
this._wrapper.appendChild(this._container);
}
else
L.Control.prototype.addTo.call(this, map);
return this;
},
onRemove: function(map) {
this._recordsCache = {};
// map.off({
// 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove
// }, this);
map.off({
// 'layeradd': this._onLayerAddRemove,
// 'layerremove': this._onLayerAddRemove
'resize': this._handleAutoresize
}, this);
},
// _onLayerAddRemove: function(e) {
// //without this, run setLayer also for each Markers!! to optimize!
// if(e.layer instanceof L.LayerGroup)
// if( L.stamp(e.layer) != L.stamp(this._layer) )
// this.setLayer(e.layer);
// },
setLayer: function(layer) { //set search layer at runtime
//this.options.layer = layer; //setting this, run only this._recordsFromLayer()
this._layer = layer;
this._layer.addTo(this._map);
return this;
},
showAlert: function(text) {
var self = this;
text = text || this.options.textErr;
this._alert.style.display = 'block';
this._alert.innerHTML = text;
clearTimeout(this.timerAlert);
this.timerAlert = setTimeout(function() {
self.hideAlert();
},this.options.autoCollapseTime);
return this;
},
hideAlert: function() {
this._alert.style.display = 'none';
return this;
},
cancel: function() {
this._input.value = '';
this._handleKeypress({ keyCode: 8 });//simulate backspace keypress
this._input.size = this._inputMinSize;
this._input.focus();
this._cancel.style.display = 'none';
this._hideTooltip();
this.fire('search:cancel');
return this;
},
expand: function(toggle) {
toggle = typeof toggle === 'boolean' ? toggle : true;
this._input.style.display = 'block';
L.DomUtil.addClass(this._container, 'search-exp');
if ( toggle !== false ) {
this._input.focus();
this._map.on('dragstart click', this.collapse, this);
}
this.fire('search:expanded');
return this;
},
collapse: function() {
this._hideTooltip();
this.cancel();
this._alert.style.display = 'none';
this._input.blur();
if(this.options.collapsed)
{
this._input.style.display = 'none';
this._cancel.style.display = 'none';
L.DomUtil.removeClass(this._container, 'search-exp');
if (this.options.hideMarkerOnCollapse) {
this._map.removeLayer(this._markerSearch);
}
this._map.off('dragstart click', this.collapse, this);
}
this.fire('search:collapsed');
return this;
},
collapseDelayed: function() { //collapse after delay, used on_input blur
var self = this;
if (!this.options.autoCollapse) return this;
clearTimeout(this.timerCollapse);
this.timerCollapse = setTimeout(function() {
self.collapse();
}, this.options.autoCollapseTime);
return this;
},
collapseDelayedStop: function() {
clearTimeout(this.timerCollapse);
return this;
},
////start DOM creations
_createAlert: function(className) {
var alert = L.DomUtil.create('div', className, this._container);
alert.style.display = 'none';
L.DomEvent
.on(alert, 'click', L.DomEvent.stop, this)
.on(alert, 'click', this.hideAlert, this);
return alert;
},
_createInput: function (text, className) {
var self = this;
var label = L.DomUtil.create('label', className, this._container);
var input = L.DomUtil.create('input', className, this._container);
input.type = 'text';
input.size = this._inputMinSize;
input.value = '';
input.autocomplete = 'off';
input.autocorrect = 'off';
input.autocapitalize = 'off';
input.placeholder = text;
input.style.display = 'none';
input.role = 'search';
input.id = input.role + input.type + input.size;
label.htmlFor = input.id;
label.style.display = 'none';
label.value = text;
L.DomEvent
.disableClickPropagation(input)
.on(input, 'keyup', this._handleKeypress, this)
.on(input, 'paste', function(e) {
setTimeout(function(e) {
self._handleKeypress(e);
},10,e);
}, this)
.on(input, 'blur', this.collapseDelayed, this)
.on(input, 'focus', this.collapseDelayedStop, this);
return input;
},
_createCancel: function (title, className) {
var cancel = L.DomUtil.create('a', className, this._container);
cancel.href = '#';
cancel.title = title;
cancel.style.display = 'none';
cancel.innerHTML = "<span>&otimes;</span>";//imageless(see css)
L.DomEvent
.on(cancel, 'click', L.DomEvent.stop, this)
.on(cancel, 'click', this.cancel, this);
return cancel;
},