"use strict";
/**
* The netgis namespace.
* @namespace
*/
var netgis = netgis || {};
/**
* The main NetGIS Client class.
* @param {Element} container
* @param {JSON} config
* @constructor
* @name Client
* @memberof netgis
*/
netgis.Client = function( container, config )
{
this.container = this.initContainer( container );
this.logEvents = false;
if ( netgis.util.isString( config ) )
{
// Config URL
this.showLoader( true );
netgis.util.request( config, this.onConfigResponse.bind( this ) );
}
else
{
// Config Object
this.init( this.container, config );
}
};
netgis.Client.prototype.init = function( container, config )
{
this.config = config;
this.initParams( config );
this.initConfig( config );
this.initElements( container );
this.initModules( config );
this.initEvents();
this.initOutput( config );
// TODO: test stuff...
var menu = new netgis.ContextMenu();
menu.attachTo( this.container );
this.modules.contextmenu = menu;
this.popup = new netgis.Popup();
this.popup.attachTo( this.container );
/*
this.popup.setPosition( 200, 300 );
this.popup.setHeader( "Popup" );
this.popup.setContent( "Hello World..." );
this.popup.show();
*/
//netgis.util.invoke( this.container, netgis.Events.TIMESLIDER_SHOW, { title: "", url: config[ "timeslider" ][ "url" ], id: config[ "timeslider" ][ "id" ] } );
};
netgis.Client.prototype.initContainer = function( container )
{
// Client Container Element
if ( netgis.util.isString( container ) )
container = document.getElementById( container );
container.classList.add( "netgis-client", "netgis-font" );
return container;
};
netgis.Client.prototype.initParams = function( config )
{
// Get Parameters
var params = window.location.search.substr( 1 );
params = params.split( "&" );
this.params = {};
for ( var i = 0; i < params.length; i++ )
{
var p = params[ i ].split( "=" );
var k = p[ 0 ].toLowerCase();
var v = p[ 1 ];
if ( ! k || k === "" ) continue;
this.params[ k ] = v;
}
// Apply Params To Config
for ( var k in this.params )
{
var v = this.params[ k ];
switch ( k )
{
case "wmc_id":
{
if ( config[ "wmc" ] )
{
config[ "wmc" ][ "id" ] = v;
}
break;
}
}
}
};
netgis.Client.prototype.initConfig = function( config )
{
// WMC
if ( config[ "wmc" ] && config[ "wmc" ][ "url" ] )
{
this.requestContextWMC( config[ "wmc" ][ "url" ], config[ "wmc" ][ "id" ] );
}
// OWS
if ( config[ "ows" ] && config[ "ows" ][ "url" ] )
{
this.requestContextOWS( config[ "ows" ][ "url" ] );
}
};
netgis.Client.prototype.initElements = function( container )
{
// Container Attributes
if ( container.hasAttribute( "data-lon" ) )
{
var lon = Number.parseFloat( container.getAttribute( "data-lon" ) );
if ( ! this.config[ "map" ][ "center_lonlat" ] ) this.config[ "map" ][ "center_lonlat" ] = [];
this.config[ "map" ][ "center_lonlat" ][ 0 ] = lon;
}
if ( container.hasAttribute( "data-lat" ) )
{
var lat = Number.parseFloat( container.getAttribute( "data-lat" ) );
if ( ! this.config[ "map" ][ "center_lonlat" ] ) this.config[ "map" ][ "center_lonlat" ] = [];
this.config[ "map" ][ "center_lonlat" ][ 1 ] = lat;
}
if ( container.hasAttribute( "data-zoom" ) )
{
var zoom = Number.parseFloat( container.getAttribute( "data-zoom" ) );
this.config[ "map" ][ "zoom" ] = zoom;
}
if ( container.hasAttribute( "data-bounds" ) )
{
var bounds = container.getAttribute( "data-bounds" );
this.config[ "tools" ][ "bounds" ] = bounds;
}
if ( container.hasAttribute( "data-editable" ) )
{
var editable = ( container.getAttribute( "data-editable" ) === "true" );
if ( ! this.config[ "tools" ] ) this.config[ "tools" ] = {};
this.config[ "tools" ][ "editable" ] = editable;
}
};
netgis.Client.prototype.initOutput = function( config )
{
if ( config[ "output" ] && config[ "output" ][ "id" ] )
{
var output = document.getElementById( config[ "output" ][ "id" ] );
if ( output && output.value && output.value.length > 0 )
{
var geojson = JSON.parse( output.value );
netgis.util.invoke( this.container, netgis.Events.MAP_EDIT_LAYER_LOADED, { geojson: geojson } );
/*this.map.addEditFeaturesGeoJSON( json, false );
var self = this;
window.setTimeout( function() {
self.map.map.updateSize();
self.map.zoomGeoJSON( json );
}, 50 );
this.editFolder.classList.remove( "netgis-hide" );
*/
}
this.output = output;
}
if ( ! this.output )
{
this.output = document.createElement( "input" );
this.output.setAttribute( "type", "hidden" );
this.output.className = "netgis-storage";
this.container.appendChild( this.output );
}
};
netgis.Client.prototype.initModules = function( config )
{
this.modules = {};
var configModules = config[ "modules" ];
if ( ! configModules ) return;
if ( configModules[ "map" ] ) this.addModule( "map", netgis.Map );
if ( configModules[ "controls" ] ) this.addModule( "controls", netgis.Controls );
if ( configModules[ "attribution" ] ) this.addModule( "attribution", netgis.Attribution );
if ( configModules[ "legend" ] ) this.addModule( "legend", netgis.Legend );
if ( configModules[ "geolocation" ] ) this.addModule( "geolocation", netgis.Geolocation );
if ( configModules[ "info" ] ) this.addModule( "info", netgis.Info );
if ( configModules[ "menu" ] ) this.addModule( "menu", netgis.Menu );
if ( configModules[ "layertree" ] ) this.addModule( "layertree", netgis.LayerTree );
/*if ( configModules[ "switcher" ] ) this.addModule( "switcher", netgis.Switcher );
if ( configModules[ "iconbar" ] ) this.addModule( "iconbar", netgis.Iconbar );*/
if ( configModules[ "searchplace" ] ) this.addModule( "searchplace", netgis.SearchPlace );
if ( configModules[ "searchparcel" ] ) this.addModule( "searchparcel", netgis.SearchParcel );
//if ( configModules[ "geolocation" ] ) this.addModule( "geolocation", netgis.Geolocation );
if ( configModules[ "toolbox" ] ) this.addModule( "toolbox", netgis.Toolbox );
if ( configModules[ "import" ] ) this.addModule( "import", netgis.Import );
if ( configModules[ "export" ] ) this.addModule( "export", netgis.Export );
if ( configModules[ "timeslider" ] ) this.addModule( "timeslider", netgis.TimeSlider );
// TODO: automate module loading from config?
// TODO: config modules script constructors?
//this.logic = new netgis.Logic( this );
};
netgis.Client.prototype.initEvents = function()
{
// Check For Event Errors
this.container.addEventListener( undefined, function( e ) { console.error( "undefined event invoked", e ); } );
// Listen To All Client Events
for ( var key in netgis.Events )
{
var val = netgis.Events[ key ];
this.container.addEventListener( val, this.handleEvent.bind( this ) );
}
// Common
/*this.container.addEventListener( "menu-button-click", this.onMenuButtonClick.bind( this ) );
this.container.addEventListener( "controls-button-click", this.onControlsButtonClick.bind( this ) );
this.container.addEventListener( "iconbar-icon-click", this.onIconbarIconClick.bind( this ) );
this.container.addEventListener( "iconbar-item-click", this.onIconbarItemClick.bind( this ) );
this.container.addEventListener( "switcher-button-click", this.onSwitcherButtonClick.bind( this ) );
this.container.addEventListener( "geolocation-toggle", this.onGeolocationToggle.bind( this ) );
this.container.addEventListener( "geolocation-change", this.onGeolocationChange.bind( this ) );*/
// TODO: move module event handling to modules?
/*
// Layer Tree
this.modules.layertree.panel.container.addEventListener( "panel-toggle", this.onLayerPanelToggle.bind( this ) );
this.modules.layertree.panel.container.addEventListener( "item-change", this.onLayerItemChange.bind( this ) );
// Search Place
this.modules.searchplace.container.addEventListener( "search-change", this.onSearchPlaceChange.bind( this ) );
this.modules.searchplace.container.addEventListener( "search-select", this.onSearchPlaceSelect.bind( this ) );
this.modules.searchplace.container.addEventListener( "search-clear", this.onSearchPlaceClear.bind( this ) );
*/
this.container.addEventListener( netgis.Events.MAP_EDIT_LAYER_CHANGE, this.onMapEditLayerChange.bind( this ) );
// Test Events
////this.container.addEventListener( netgis.Events.MAP_CLICK, this.onMapClick.bind( this ) );
};
netgis.Client.prototype.showLoader = function( on )
{
if ( ! this.loader )
{
this.loader = document.createElement( "div" );
this.loader.className = "netgis-loader netgis-color-e netgis-text-a";
this.loader.innerHTML = "<i class='fas fa-cog'></i>";
if ( this.config[ "client" ] && this.config[ "client" ][ "loading_text" ] )
this.loader.innerHTML += "<h2>" + this.config[ "client" ][ "loading_text" ] + "</h2>";
this.container.appendChild( this.loader );
}
if ( on === false )
{
this.loader.classList.add( "netgis-fade" );
this.loaderTimeout = window.setTimeout( function() { this.loader.classList.add( "netgis-hide" ); this.loaderTimeout = null; }.bind( this ), 600 );
}
else
{
this.loader.classList.remove( "netgis-hide" );
this.loader.classList.remove( "netgis-fade" );
if ( this.loaderTimeout )
{
window.clearTimeout( this.loaderTimeout );
this.loaderTimeout = null;
}
}
};
netgis.Client.prototype.handleEvent = function( e )
{
var type = e.type;
var params = e.detail;
if ( this.logEvents === true ) console.info( "EVENT:", type, params );
//console.trace( "EVENT:", type, params );
/*for ( var key in this.modules )
{
var module = this.modules[ key ];
if ( module.handleEvent ) module.handleEvent( type, params );
}*/
};
netgis.Client.prototype.addModule = function( id, construct )
{
var module = new construct( /*this,*/ this.config );
if ( module.attachTo ) module.attachTo( this.container );
this.modules[ id ] = module;
return module;
};
netgis.Client.prototype.isMobile = function()
{
//return ( document.body.getBoundingClientRect().width < 600 );
//return ( this.container.getBoundingClientRect().width < 600 );
return netgis.util.isMobile( this.container );
};
netgis.Client.prototype.onConfigResponse = function( data )
{
var config = JSON.parse( data );
this.init( this.container, config );
this.showLoader( false );
};
netgis.Client.prototype.requestContextWMC = function( url, id )
{
if ( url.indexOf( "{id}" ) > -1 )
{
if ( ! id )
{
console.warn( "No WMC id set in config for url", url );
return;
}
else
{
url = netgis.util.replace( url, "{id}", id );
}
}
var wmc = new netgis.WMC();
wmc.requestContext( url, this.onContextResponseWMC.bind( this ) );
this.showLoader( true );
};
netgis.Client.prototype.onContextResponseWMC = function( context )
{
console.info( "WMC Response:", context );
// TODO: pass only final config instead of context ?
// Apply Changes To Current Config
for ( var i = 0; i < context.config.layers.length; i++ )
{
var layer = context.config.layers[ i ];
this.config[ "layers" ].push( layer );
}
if ( context.config[ "map" ][ "bbox" ] )
{
this.config[ "map" ][ "bbox" ] = context.config[ "map" ][ "bbox" ];
}
// Update Modules
netgis.util.invoke( this.container, netgis.Events.CLIENT_CONTEXT_RESPONSE, { context: context } );
this.showLoader( false );
};
netgis.Client.prototype.requestContextOWS = function( url )
{
console.info( "Request OWS:", url );
netgis.util.request( url, this.onContextResponseOWS.bind( this ) );
};
netgis.Client.prototype.onContextResponseOWS = function( data )
{
var json = JSON.parse( data );
console.info( "OWS Response:", json );
var config = netgis.OWS.read( json, this );
console.info( "OWS Config:", config );
};
/*
netgis.Client.prototype.onMenuButtonClick = function( e )
{
var params = e.detail;
switch ( params[ "id" ] )
{
case "layertree":
{
this.modules.layertree.panel.toggle();
break;
}
case "searchplace":
{
this.modules.searchplace.search.toggle();
break;
}
default:
{
console.error( "unhandled menu button", params );
break;
}
}
};
*/
/*
netgis.Client.prototype.onControlsButtonClick = function( e )
{
var params = e.detail;
switch ( params.id )
{
case "zoom_in":
{
this.modules.map.zoom( 1.0 );
break;
}
case "zoom_out":
{
this.modules.map.zoom( -1.0 );
break;
}
case "zoom_gps":
{
this.modules.geolocation.setActive( ! this.modules.geolocation.isActive() );
break;
}
case "zoom_home":
{
var lonlat = this.config[ "map" ][ "centerLonLat" ];
this.modules.map.zoomLonLat( lonlat[ 0 ], lonlat[ 1 ], this.config[ "map" ][ "zoom" ] );
break;
}
}
};
*/
/*
netgis.Client.prototype.onLayerPanelToggle = function( e )
{
var params = e.detail;
if ( params.visible )
this.modules.iconbar.hide();
else
this.modules.iconbar.show();
};
*/
/*
netgis.Client.prototype.onLayerItemChange = function( e )
{
var params = e.detail;
if ( params.checked )
{
var layers = this.config[ "layers" ];
for ( var i = 0; i < layers.length; i++ )
{
var layer = layers[ i ];
if ( layer[ "id" ] !== params.id ) continue;
this.modules.map.addLayer( params.id, layer );
}
}
else
{
this.modules.map.removeLayer( params.id );
}
// Shift Switcher Items
if ( this.modules.switcher.getIndex( params.id ) === 0 ) this.modules.switcher.shift( 1, 0 );
};
*/
netgis.Client.prototype.onIconbarIconClick = function( e )
{
var params = e.detail;
switch ( params.id )
{
case "home":
{
var layers = this.config[ "layers" ];
for ( var i = 0; i < layers.length; i++ )
{
var layer = layers[ i ];
var id = layer[ "id" ];
if ( layer[ "active" ] === true )
{
this.modules.map.addLayer( id, layer );
this.modules.layertree.tree.setItemChecked( id, true );
}
else
{
this.modules.map.removeLayer( id );
this.modules.layertree.tree.setItemChecked( id, false );
}
}
break;
}
}
};
netgis.Client.prototype.onIconbarItemClick = function( e )
{
var params = e.detail;
var layers = this.config[ "layers" ];
for ( var i = 0; i < layers.length; i++ )
{
var layer = layers[ i ];
var id = layer[ "id" ];
if ( layer[ "folder" ] === "background" ) continue;
if ( id === params.id )
{
this.modules.map.addLayer( id, layer );
this.modules.layertree.tree.setItemChecked( id, true );
}
else
{
this.modules.map.removeLayer( id );
this.modules.layertree.tree.setItemChecked( id, false );
}
}
};
netgis.Client.prototype.onSwitcherButtonClick = function( e )
{
var params = e.detail;
var buttons = this.config[ "switcher" ][ "buttons" ];
var layers = this.config[ "layers" ];
for ( var i = 0; i < buttons.length; i++ )
{
var button = buttons[ i ];
var id = button[ "id" ];
if ( id === params.id )
{
for ( var j = 0; j < layers.length; j++ )
{
var layer = layers[ j ];
if ( layer[ "id" ] !== id ) continue;
this.modules.map.addLayer( id, layer );
this.modules.layertree.tree.setItemChecked( id, true );
}
}
else
{
this.modules.map.removeLayer( id );
this.modules.layertree.tree.setItemChecked( id, false );
}
}
// Shift Switcher Items
if ( this.modules.switcher.getIndex( params.id ) === 0 ) this.modules.switcher.shift( 1, 0 );
};
/*
netgis.Client.prototype.onSearchPlaceChange = function( e )
{
var params = e.detail;
var url = this.config[ "searchplace" ][ "url" ];
url = netgis.util.replace( url, "{query}", window.encodeURIComponent( params.query ) );
netgis.util.request( url, this.onSearchPlaceResponse.bind( this ) );
};
netgis.Client.prototype.onSearchPlaceResponse = function( data )
{
var json = JSON.parse( data );
var results = json[ "data" ];
this.modules.searchplace.search.clearResults();
for ( var i = 0; i < results.length; i++ )
{
var result = results[ i ];
var title = result[ "name" ];
var resultData =
{
type: "street",
id: result[ "strid" ],
lon: Number.parseFloat( result[ "wgs_x" ] ),
lat: Number.parseFloat( result[ "wgs_y" ] )
};
this.modules.searchplace.search.addResult( title, JSON.stringify( resultData ) );
}
};
netgis.Client.prototype.onSearchPlaceSelect = function( e )
{
var params = e.detail;
var data = JSON.parse( params.data );
this.modules.map.zoomLonLat( data.lon, data.lat, this.config[ "searchplace" ][ "zoom" ] );
this.modules.map.setSearchMarkerLonLat( data.lon, data.lat );
this.modules.map.setSearchMarkerVisible( true );
// Search Detail Request
if ( data.type === "street" )
{
var url = this.config[ "searchplace" ][ "url_detail" ];
if ( url )
{
// TODO: replace all result props to support any url variable (like popup html)?
url = netgis.util.replace( url, "{id}", data.id );
netgis.util.request( url, this.onSearchPlaceDetailResponse.bind( this ) );
}
}
};
netgis.Client.prototype.onSearchPlaceDetailResponse = function( data )
{
var json = JSON.parse( data );
var results = json[ "hsnrarr" ];
if ( results.length === 0 ) return;
this.modules.searchplace.search.clearResults();
this.modules.map.setSearchMarkerVisible( true );
for ( var i = 0; i < results.length; i++ )
{
var result = results[ i ];
var title = json[ "strname" ] + " " + result[ "hsnr" ];
var resultData =
{
type: "address",
lon: Number.parseFloat( result[ "wgs_x" ] ),
lat: Number.parseFloat( result[ "wgs_y" ] )
};
this.modules.searchplace.search.addResult( title, JSON.stringify( resultData ) );
}
};
netgis.Client.prototype.onSearchPlaceClear = function( e )
{
this.modules.map.setSearchMarkerVisible( false );
};
*/
netgis.Client.prototype.onGeolocationToggle = function( e )
{
var params = e.detail;
this.modules.map.setGeolocMarkerVisible( params.on );
};
netgis.Client.prototype.onGeolocationChange = function( e )
{
var params = e.detail;
this.modules.map.zoomLonLat( params.lon, params.lat, this.config[ "geolocation" ][ "zoom" ] );
this.modules.map.setGeolocMarkerLonLat( params.lon, params.lat );
//this.modules.geolocation.setActive( false );
};
netgis.Client.prototype.onMapEditLayerChange = function( e )
{
var params = e.detail;
var geojson = JSON.stringify( params.geojson );
this.output.value = geojson;
//this.attribution.onEditFeaturesChange( e.detail );
};
netgis.Client.handleCommand = function( src, command )
{
// TODO: having a common event invoke scheme for buttons, inputs, items etc. would get rid of this ?
//console.info( "Handle Command:", arguments );
// Translate Command IDs To Events
switch ( command )
{
case netgis.Commands.LAYERTREE:
{
netgis.util.invoke( src, netgis.Events.LAYERTREE_TOGGLE, null );
break;
}
case netgis.Commands.SEARCHPLACE:
{
netgis.util.invoke( src, netgis.Events.SEARCHPLACE_TOGGLE, null );
break;
}
case netgis.Commands.SEARCHPARCEL:
{
netgis.util.invoke( src, netgis.Events.SEARCHPARCEL_TOGGLE, null );
//netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.SEARCH_PARCEL } );
break;
}
case netgis.Commands.TOOLBOX:
{
netgis.util.invoke( src, netgis.Events.TOOLBOX_TOGGLE, null );
break;
}
case netgis.Commands.LEGEND:
{
netgis.util.invoke( src, netgis.Events.LEGEND_TOGGLE, null );
break;
}
case netgis.Commands.VIEW_PREV:
{
netgis.util.invoke( src, netgis.Events.MAP_VIEW_PREV, null );
break;
}
case netgis.Commands.VIEW_NEXT:
{
netgis.util.invoke( src, netgis.Events.MAP_VIEW_NEXT, null );
break;
}
case netgis.Commands.VIEW:
{
netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.VIEW } );
break;
}
case netgis.Commands.ZOOM_BOX:
{
netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.ZOOM_BOX } );
break;
}
case netgis.Commands.ZOOM_SCALE:
{
var text = src.innerText;
var scale = Number.parseInt( text.split( ":" )[ 1 ] );
netgis.util.invoke( src, netgis.Events.MAP_ZOOM_SCALE, { scale: scale, anim: true } );
break;
}
case netgis.Commands.MEASURE_LINE:
{
netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.MEASURE_LINE } );
break;
}
case netgis.Commands.MEASURE_AREA:
{
netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.MEASURE_AREA } );
break;
}
case netgis.Commands.MEASURE_CLEAR:
{
netgis.util.invoke( src, netgis.Events.MEASURE_CLEAR, null );
break;
}
case netgis.Commands.DRAW_POINTS:
{
netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.DRAW_POINTS } );
break;
}
case netgis.Commands.DRAW_LINES:
{
netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.DRAW_LINES } );
break;
}
case netgis.Commands.DRAW_POLYGONS:
{
netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.DRAW_POLYGONS } );
break;
}
case netgis.Commands.MODIFY_FEATURES:
{
netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.MODIFY_FEATURES } );
break;
}
case netgis.Commands.DELETE_FEATURES:
{
netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.DELETE_FEATURES } );
break;
}
case netgis.Commands.BUFFER_FEATURES:
{
//netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.BUFFER_FEATURES } );
netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.BUFFER_FEATURES_DYNAMIC } );
break;
}
case netgis.Commands.CUT_FEATURES:
{
netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.CUT_FEATURES } );
//netgis.util.invoke( src, netgis.Events.CLIENT_SET_MODE, { mode: netgis.Modes.CUT_FEATURES_DYNAMIC } );
break;
}
case netgis.Commands.IMPORT_LAYER:
{
netgis.util.invoke( src, netgis.Events.IMPORT_LAYER_SHOW, null );
break;
}
case netgis.Commands.EXPORT:
{
netgis.util.invoke( src, netgis.Events.EXPORT_SHOW, null );
break;
}
case netgis.Commands.GEOLOCATION:
{
netgis.util.invoke( src, netgis.Events.GEOLOCATION_SHOW_OPTIONS, null );
break;
}
default:
{
console.error( "unhandled command id", command );
break;
}
}
};