gemapi.js

var Module;
var SDK_version = "7.1.67BD45A3";
/**
 * 
 * @private
 * @returns {string}
 */
function getBaseURL() {
	var originURL = window.location.origin;
	if (originURL.localeCompare("https://new.magiclane.com") === 0)
		return originURL + "/";
	else
		if (originURL.localeCompare("https://internal.magiclane.com") === 0)
			return originURL + "/";
		else
			if (originURL.localeCompare("https://developer.magiclane.com") === 0)
				return originURL + "/";
			else
				if (originURL.localeCompare("https://new.generalmagic.com") === 0)
					return originURL + "/";
				else
					if (originURL.localeCompare("https://internal.generalmagic.com") === 0)
						return originURL + "/";
					else
						if (originURL.localeCompare("https://developer.generalmagic.com") === 0)
							return originURL + "/";
						else
							if (originURL.localeCompare("https://intranet.generalmagic.com") === 0)
								return originURL + "/";
						else
							if (originURL.localeCompare("https://testing.magiclane.com") === 0)
								return originURL + "/";
	return "https://www.magiclane.com/";
}
var baseURL = getBaseURL();
var sourceWebsite = baseURL + "sdk/js/" + SDK_version + "/";


/**
 * @namespace gem
 * @class gem
*/
class gem {

}

/**
 *@namespace gem.core
 *@alias gem.core
 *@hideconstructor
 *@memberof gem
 */
class core {

}
gem.core = core;
/**Routing And Navigation Namespace
 * @namespace 
 * @alias gem.routesAndNavigation
 * @hideconstructor
 * @memberof gem
 */
gem.routesAndNavigation = class routingandnavigation {
	static getEVCarModels() {
		return new gem.routesAndNavigation.EVCarModelsList(Module.getEVCarModels());
	}
}
/**
 * @class gem.d3Scene
 * @namespace gem.d3Scene
 * @hideconstructor
 * @memberof gem
 */
gem.d3Scene = class _3dScene {

}

/**
 * @class gem.content
 * @namespace gem.content
 * @hideconstructor
 * @memberof gem

 */
gem.content = class content {

}
/**
 * @typedef {object} gem.core.Xy
 * @property {number} x - x coordinate
 * @property {number} y - y coordinate
 */

/**
 * @typedef {object} gem.core.Coordinates
 * @property {number} latitude - between -90 -> 90
 * @property {number} longitude - between -180 -> 180
 * @property {number} altitude - altitude
 * @property {number} bearing - bearing Angle between 0 - 360
 */

/**
 * @typedef {object} gem.core.RectangleFloat
 * @property {number} x - bottom left X value
 * @property {number} y - bottom right Y value
 * @property {number} width - width of the rectangle
 * @property {number} height - height of the rectangle
 */

/**
 * @typedef {object} gem.core.RectangleInteger
 * @property {number} x - bottom left X value
 * @property {number} y - bottom right Y value
 * @property {number} width - width of the rectangle
 * @property {number} height - height of the rectangle
 */

/**
 * @typedef {object} gem.core.ColorInfo
 * @property {number} r - red channel (0-255)
 * @property {number} g - green channel (0-255)
 * @property {number} b - blue channel (0-255)
 * @property {number} a - alpha channel (0-255)
 */

/**
 * @typedef {object} gem.core.TimeAndDistance
 * @property {number} totaltime 
 * @property {number} totaldistance
 */

/**
 * @typedef {object} gem.routesAndNavigation.SurfaceSection
 * @property {number} startDistanceM - Distance in meters where the section starts
 * @property {gem.routesAndNavigation.ESurfaceType} categ - The type of surface
 */

/**
 * @typedef {object} gem.routesAndNavigation.RoadTypeSection
 * @property {number} startDistanceM - Distance in meters where the section starts
 * @property {gem.routesAndNavigation.ERoadType} categ - The type of road
 */

/**
 * @typedef {object} gem.routesAndNavigation.SteepSection
 * @property {number} startDistanceM - Distance in meters where the section starts
 * @property {number} categ - The category of steep
 */

/**
 * @typedef {object} gem.core.BoundingBox
 * @property {gem.core.Coordinates} leftTop - left top corner
 * @property {gem.core.Coordinates} rightBottom - right bottom Corner
 * @property {boolean} isEmpty - if it valid or not
 */

/**
 * @typedef {object} gem.core.WikiDescriptionListener
 */

/** @typedef {object} gem.routesAndNavigation.ElectricBikeProfile
 *  @property {gem.routesAndNavigation.EEBikeType} type
 *  @property {number} bikeMass   - Bike mass in kg. If 0.0 , a default value is used
 *  @property {number} bikerMass - Biker mass in kg. If 0.0 , a default value is used
 *  @property {number} auxConsumptionDay -  Bike auxiliary power consumption during day in Watts. If 0.0, a default value is used
 *  @property {number} auxConsumptionNight - Bike auxiliary power consumption during night in Watts. If 0.0, a default value is used
 *  @property {boolean} ignoreLegalRestrictions - Ignore country based legal restrictions related to e-bikes
*/
/**
 * Emscripten wrapper for object class 
 * @class Em_Object
 */

class Em_Object {

	/**
	 * Constructor
	 * @param {object} origObject - Emscripten object 
	 */
	constructor(origObject) {
		this.m_object = origObject;
	}
	/**
	 *calls emscripten object destructor
	 */
	delete() {
		this.m_object.delete();
	}
	/**Check if emscripten object has been deleted
	 * @returns {boolean}
	 */
	isDeleted() {
		return this.m_object.isDeleted();
	}
	/**Returns emscripten object
	 * @returns {object}
	 */
	getRawPointer() {
		return this.m_object;
	}

	getFormattedText() {

	}
	getValue() {

	}
}
/**Emscripten wrapper to vector class
 * @class Em_Vector
 * @augments Em_Object
 */
class Em_Vector extends Em_Object {
	/**
	 * Get item at position
	 * @param {number} position -  position in vector 
	 * @returns {object} - Returns item at position
	 */
	get(position) {
		return this.m_object.get(number);
	}
	/**
	 * Returns vector size
	 * @returns {number}*/
	size() {
		return this.m_object.size();
	}
	/**
	 * 
	 * @param {object} object 
	 */
	push_back(object) {
		this.m_object.push_back(object);
	}
}


/**
 * @class Em_Enum
 * @param {number} value
 * @hideconstructor
 */
class Em_Enum {
	constructor(value) {
		this.value = value;
	}
}

/**
 * @class EAddressDetailLevel
 * @memberof gem.core
 * @alias gem.core.EAddressDetailLevel
 * @description A class that represents the level of detail for an address.
 * @hideconstructor
 */
gem.core.EAddressDetailLevel = class TAddressDetailLevel {

	static initializeData() {
		/**
		 * @memberof gem.core.EAddressDetailLevel
		 * @property {Em_Enum} gem.core.EAddressDetailLevel.EAD_NoDetail - No address details available.
		 */
		gem.core.EAddressDetailLevel.EAD_NoDetail = (Module.TAddressDetailLevel.EAD_NoDetail);
		/**
		 * @memberof gem.core.EAddressDetailLevel
		 * @property {Em_Enum} gem.core.EAddressDetailLevel.EAD_Country - Country
		 */
		gem.core.EAddressDetailLevel.EAD_Country = (Module.TAddressDetailLevel.EAD_Country);
		/**
		 * @memberof gem.core.EAddressDetailLevel
		 *@property {Em_Enum} gem.core.EAddressDetailLevel.EAD_State - State or province.
		 */
		gem.core.EAddressDetailLevel.EAD_State = (Module.TAddressDetailLevel.EAD_State);
		/**
		 * @memberof gem.core.EAddressDetailLevel
		 *@property {Em_Enum} gem.core.EAddressDetailLevel.EAD_County - County, which is an entity between a state and a city.
		 */
		gem.core.EAddressDetailLevel.EAD_County = (Module.TAddressDetailLevel.EAD_County);
		/**
		 * @memberof gem.core.EAddressDetailLevel
		 * @property {Em_Enum} gem.core.EAddressDetailLevel.EAD_District -Municipal district.
		 */
		gem.core.EAddressDetailLevel.EAD_District = (Module.TAddressDetailLevel.EAD_District);
		/**
		 * @memberof gem.core.EAddressDetailLevel
		 * @property {Em_Enum} gem.core.EAddressDetailLevel.EAD_City -Town or city.
		 */
		gem.core.EAddressDetailLevel.EAD_City = (Module.TAddressDetailLevel.EAD_City);
		/**
		* @memberof gem.core.EAddressDetailLevel
		 * @property {Em_Enum} gem.core.EAddressDetailLevel.EAD_Settlement - Settlement.
		 */
		gem.core.EAddressDetailLevel.EAD_Settlement = (Module.TAddressDetailLevel.EAD_Settlement);
		/**
		* @memberof gem.core.EAddressDetailLevel
		 * @property {Em_Enum} gem.core.EAddressDetailLevel.EAD_PostalCode - Postal Code
		 */
		gem.core.EAddressDetailLevel.EAD_PostalCode = (Module.TAddressDetailLevel.EAD_PostalCode);
		/**
		* @memberof gem.core.EAddressDetailLevel
		 * @property {Em_Enum} gem.core.EAddressDetailLevel.EAD_Street - Street/road name.
		 */
		gem.core.EAddressDetailLevel.EAD_Street = (Module.TAddressDetailLevel.EAD_Street);
		/**
		 * @memberof gem.core.EAddressDetailLevel
		 * @property {Em_Enum} gem.core.EAddressDetailLevel.EAD_StreetSection - Street section subdivision.
		 */
		gem.core.EAddressDetailLevel.EAD_StreetSection = (Module.TAddressDetailLevel.EAD_StreetSection);
		/**
		* @memberof gem.core.EAddressDetailLevel
		 *@property {Em_Enum} gem.core.EAddressDetailLevel.EAD_StreetLane -  Street lane subdivision.
		 */
		gem.core.EAddressDetailLevel.EAD_StreetLane = (Module.TAddressDetailLevel.EAD_StreetLane);
		/**
		* @memberof gem.core.EAddressDetailLevel
		 * @property {Em_Enum} gem.core.EAddressDetailLevel.EAD_StreetAlley -Street alley subdivision.
		 */
		gem.core.EAddressDetailLevel.EAD_StreetAlley = (Module.TAddressDetailLevel.EAD_StreetAlley);
		/**
		* @memberof gem.core.EAddressDetailLevel
		 *@property {Em_Enum} gem.core.EAddressDetailLevel.EAD_HouseNumber -  Address field denoting house number.
		 */
		gem.core.EAddressDetailLevel.EAD_HouseNumber = (Module.TAddressDetailLevel.EAD_HouseNumber);
		/**
		 * @memberof gem.core.EAddressDetailLevel
		 *@property {Em_Enum} gem.core.EAddressDetailLevel.EAD_Crossing - Address field denoting a street in a crossing.
		 */
		gem.core.EAddressDetailLevel.EAD_Crossing = (Module.TAddressDetailLevel.EAD_Crossing);
	}
	static convertToInternal(number) {
		switch (number) {
			case Module.TAddressDetailLevel.EAD_NoDetail.value:
				return Module.TAddressDetailLevel.EAD_NoDetail;
			case Module.TAddressDetailLevel.EAD_Country.value:
				return Module.TAddressDetailLevel.EAD_Country;
			case Module.TAddressDetailLevel.EAD_State.value:
				return Module.TAddressDetailLevel.EAD_State;
			case Module.TAddressDetailLevel.EAD_County.value:
				return Module.TAddressDetailLevel.EAD_County;
			case Module.TAddressDetailLevel.EAD_District.value:
				return Module.TAddressDetailLevel.EAD_District;
			case Module.TAddressDetailLevel.EAD_City.value:
				return Module.TAddressDetailLevel.EAD_City;
			case Module.TAddressDetailLevel.EAD_Settlement.value:
				return Module.TAddressDetailLevel.EAD_Settlement;
			case Module.TAddressDetailLevel.EAD_PostalCode.value:
				return Module.TAddressDetailLevel.EAD_PostalCode;
			case Module.TAddressDetailLevel.EAD_Street.value:
				return Module.TAddressDetailLevel.EAD_Street;
			case Module.TAddressDetailLevel.EAD_StreetSection.value:
				return Module.TAddressDetailLevel.EAD_StreetSection;
			case Module.TAddressDetailLevel.EAD_StreetLane.value:
				return Module.TAddressDetailLevel.EAD_StreetLane;
			case Module.TAddressDetailLevel.EAD_StreetAlley.value:
				return Module.TAddressDetailLevel.EAD_StreetAlley;
			case Module.TAddressDetailLevel.EAD_HouseNumber.value:
				return Module.TAddressDetailLevel.EAD_HouseNumber;
			case Module.TAddressDetailLevel.EAD_Crossing.value:
				return Module.TAddressDetailLevel.EAD_Crossing;
		}
	}
}
const EViewDataTransitionStatus = {
	VD_Complete: 0,
	VD_IncompleteOnline: 1
};
const EViewCameraTransitionStatus = {
	CT_Stationary: 0,
	CT_Moving: 1
}
/**
 * Enumeration for different types of address fields.
 * @enum {number}
 * @property {number} Extension - Represents the extension field of an address.
 * @property {number} BuildingFloor - Represents the building floor field of an address.
 * @property {number} BuildingName - Represents the building name field of an address.
 * @property {number} BuildingRoom - Represents the building room field of an address.
 * @property {number} BuildingZone - Represents the building zone field of an address.
 * @property {number} StreetName - Represents the street name field of an address.
 * @property {number} StreetNumber - Represents the street number field of an address.
 * @property {number} PostalCode - Represents the postal code field of an address.
 * @property {number} Settlement - Represents the settlement field of an address.
 * @property {number} City - Represents the city field of an address.
 * @property {number} County - Represents the county field of an address.
 * @property {number} State - Represents the state field of an address.
 * @property {number} StateCode - Represents the state code field of an address.
 * @property {number} Country - Represents the country field of an address.
 * @property {number} CountryCode - Represents the country code field of an address.
 * @property {number} District - Represents the district field of an address.
 * @property {number} Crossing1 - Represents the first crossing field of an address.
 * @property {number} Crossing2 - Represents the second crossing field of an address.
 * @property {number} SegmentName - Represents the segment name field of an address.
 * @property {number} AddrLast - Represents the last address field.
 */
const EAddressField = {
	Extension: 0,
	BuildingFloor: 1,
	BuildingName: 2,
	BuildingRoom: 3,
	BuildingZone: 4,
	StreetName: 5,
	StreetNumber: 6,
	PostalCode: 7,
	Settlement: 8,
	City: 9,
	County: 10,
	State: 11,
	StateCode: 12,
	Country: 13,
	CountryCode: 14,
	District: 15,
	Crossing1: 16,
	Crossing2: 17,
	SegmentName: 18,
	AddrLast: 19,
};
gem.core.EAddressField = EAddressField;
/**
 * @class gem.routesAndNavigation.ERouteType
 * @description Route Type
 * @property {Em_Enum} gem.routesAndNavigation.ERouteType.ERT_Fastest - Route type - fastest route type
 * @property {Em_Enum} gem.routesAndNavigation.ERouteType.ERT_Shortest - Route type - shortest route type
 * @property {Em_Enum} gem.routesAndNavigation.ERouteType.ERT_Economic - Route type - economical route type
 * @hideconstructor
 */
gem.routesAndNavigation.ERouteType = class TRouteType {
	static ERT_Fastest() {
		return gem.routesAndNavigation.ERouteType.ERT_Fastest;
	}
	static ERT_Shortest() {
		return gem.routesAndNavigation.ERouteType.ERT_Shortest;
	}
	static ERT_Economic() {
		return gem.routesAndNavigation.ERouteType.ERT_Economic;
	}
	static initializeData() {
		gem.routesAndNavigation.ERouteType.ERT_Fastest = Module.RouteType.ERT_Fastest;
		gem.routesAndNavigation.ERouteType.ERT_Shortest = Module.RouteType.ERT_Shortest;
		gem.routesAndNavigation.ERouteType.ERT_Economic = Module.RouteType.ERT_Economic;
	}
}
/**
 * @class gem.routesAndNavigation.ERouteTransportMode
 * @description Transport Mode
 * @hideconstructor
 * @property {Em_Enum} gem.routesAndNavigation.ERouteTransportMode.ETM_Car - car
 * @property {Em_Enum} gem.routesAndNavigation.ERouteTransportMode.ETM_Lorry - lorry/truck
 * @property {Em_Enum} gem.routesAndNavigation.ERouteTransportMode.ETM_Bicycle - bicycle
 * @property {Em_Enum} gem.routesAndNavigation.ERouteTransportMode.ETM_Pedestrian - on foot
 * @property {Em_Enum} gem.routesAndNavigation.ERouteTransportMode.ETM_Public - public transport
 * @property {Em_Enum} gem.routesAndNavigation.ERouteTransportMode.ETM_Uber - uber
 * @property {Em_Enum} gem.routesAndNavigation.ERouteTransportMode.ETM_Shared_Vehicles - shared vehicle
 */
gem.routesAndNavigation.ERouteTransportMode = class TRouteTransportMode {
	static ETM_Public() {
		return gem.routesAndNavigation.ERouteTransportMode.ETM_Public;
	}
	static ETM_Lorry() {
		return gem.routesAndNavigation.ERouteTransportMode.ETM_Lorry;
	}
	static ETM_Bicycle() {
		return gem.routesAndNavigation.ERouteTransportMode.ETM_Bicycle;
	}
	static ETM_Pedestrian() {
		return gem.routesAndNavigation.ERouteTransportMode.ETM_Pedestrian;
	}
	static ETM_Car() {
		return gem.routesAndNavigation.ERouteTransportMode.ETM_Car;
	}
	static ETM_Uber() {
		return gem.routesAndNavigation.ERouteTransportMode.ETM_Uber;
	}
	static ETM_Shared_Vehicles() {
		return gem.routesAndNavigation.ERouteTransportMode.ETM_Shared_Vehicles;
	}
	static initializeData() {
		gem.routesAndNavigation.ERouteTransportMode.ETM_Car = Module.TransportMode.ETM_Car;
		gem.routesAndNavigation.ERouteTransportMode.ETM_Lorry = Module.TransportMode.ETM_Lorry;
		gem.routesAndNavigation.ERouteTransportMode.ETM_Bicycle = Module.TransportMode.ETM_Bicycle;
		gem.routesAndNavigation.ERouteTransportMode.ETM_Pedestrian = Module.TransportMode.ETM_Pedestrian;
		gem.routesAndNavigation.ERouteTransportMode.ETM_Public = Module.TransportMode.ETM_Public;
		gem.routesAndNavigation.ERouteTransportMode.ETM_Uber = Module.TransportMode.ETM_Uber;
		gem.routesAndNavigation.ERouteTransportMode.ETM_Shared_Vehicles = Module.TransportMode.ETM_Shared_Vehicles;
	}
}
/**
 * @class gem.d3Scene.EMarkerType
 * @description Data source geometry type
 * @hideconstructor
 */
gem.d3Scene.EMarkerType = class TVectorDataType {
	static initializeData() {
		/**
		 *@property {Em_Enum} gem.d3Scene.EMarkerType.EVDT_Point -  contains multi-points items
		 */
		gem.d3Scene.EMarkerType.EVDT_Point = Module.TVectorDataType.EVDT_Point;
		/**
		 * @property {Em_Enum} gem.d3Scene.EMarkerType.EVDT_Polyline -  contains polyline items
		 */
		gem.d3Scene.EMarkerType.EVDT_Polygon = Module.TVectorDataType.EVDT_Polygon;
		/**
		 *@property {Em_Enum} gem.d3Scene.EMarkerType.EVDT_Polygon -  contains polygon items
		 */
		gem.d3Scene.EMarkerType.EVDT_Polyline = Module.TVectorDataType.EVDT_Polyline;

	}
}
/**
 * @class gem.d3Scene.EUsableIcons 
 * @description API predefined icons ids
 * @hideconstructor
 */
gem.d3Scene.EUsableIcons = class TUsableIcons {
	static initializeData() {
		/**
		 *@property {Em_Enum} gem.d3Scene.EUsableIcons.eNavigationLayoutBgr_SpeedAlarm
		 */
		gem.d3Scene.EUsableIcons.eNavigationLayoutBgr_SpeedAlarm = Module.Icons.eNavigationLayoutBgr_SpeedAlarm;
		/**
		 * @property {Em_Enum} gem.d3Scene.EUsableIcons.eSearch_Results_Pin
		 */
		gem.d3Scene.EUsableIcons.eSearch_Results_Pin = Module.Icons.eSearch_Results_Pin;
		/**
		 * @property {Em_Enum} gem.d3Scene.EUsableIcons.PublicTransport_Bus
		 */
		gem.d3Scene.EUsableIcons.PublicTransport_Bus = Module.Icons.PublicTransport_Bus;
		/**
		 * @property {Em_Enum} gem.d3Scene.EUsableIcons.PublicTransport_Underground
		 */
		gem.d3Scene.EUsableIcons.PublicTransport_Underground = Module.Icons.PublicTransport_Underground;
		/**
		 * @property {Em_Enum} gem.d3Scene.EUsableIcons.PublicTransport_Train
		 */
		gem.d3Scene.EUsableIcons.PublicTransport_Train = Module.Icons.PublicTransport_Train;
		/**
		 * @property {Em_Enum} gem.d3Scene.EUsableIcons.PublicTransport_Tram
		 */
		gem.d3Scene.EUsableIcons.PublicTransport_Tram = Module.Icons.PublicTransport_Tram;
		/**
		 * @property {Em_Enum} gem.d3Scene.EUsableIcons.PublicTransport_Water
		 */
		gem.d3Scene.EUsableIcons.PublicTransport_Water = Module.Icons.PublicTransport_Water;
		/**
		 * @property {Em_Enum} gem.d3Scene.EUsableIcons.PublicTransport_Other
		 */
		gem.d3Scene.EUsableIcons.PublicTransport_Other = Module.Icons.PublicTransport_Other;
		/**
		 * @property {Em_Enum} gem.d3Scene.EUsableIcons.PublicTransport_Walk
		 */
		gem.d3Scene.EUsableIcons.PublicTransport_Walk = Module.Icons.PublicTransport_Walk;
		/**
		 * @property {Em_Enum} gem.d3Scene.EUsableIcons.YellowBall
		 */
		gem.d3Scene.EUsableIcons.YellowBall = Module.Icons.YellowBall;
		/**
		 * @property {Em_Enum} gem.d3Scene.EUsableIcons.BlueBall
		 */
		gem.d3Scene.EUsableIcons.BlueBall = Module.Icons.BlueBall;
		/**
		 * @property {Em_Enum} gem.d3Scene.EUsableIcons.Waypoint_Start
		 */
		gem.d3Scene.EUsableIcons.Waypoint_Start = Module.Icons.Waypoint_Start;
		/**
	 * @property {Em_Enum} gem.d3Scene.EUsableIcons.Waypoint_Intermediary
	 */
		gem.d3Scene.EUsableIcons.Waypoint_Intermediary = Module.Icons.Waypoint_Intermediary;
		/**
		* @property {Em_Enum} gem.d3Scene.EUsableIcons.Waypoint_Finish
		 */
		gem.d3Scene.EUsableIcons.Waypoint_Finish = Module.Icons.Waypoint_Finish;
	}
}

/**
 * @class gem.d3Scene.ECursorEvent
 * @description Cursor Event type on a touch event(click event) handler
 * @hideconstructor
 */
gem.d3Scene.ECursorEvent = class TCursorEvent {
	static initializeData() {
		/**
		 *@property {Em_Enum} gem.d3Scene.ECursorEvent.eCursorEvent_Down - cursor has been touch down
		 */
		gem.d3Scene.ECursorEvent.eCursorEvent_Down = Module.CursorEvent.eCursorEvent_Down;
		/**
		 * @property {Em_Enum} gem.d3Scene.ECursorEvent.eCursorEvent_Move - cursor has been moved
		 */
		gem.d3Scene.ECursorEvent.eCursorEvent_Move = Module.CursorEvent.eCursorEvent_Move;
		/**
		 * @property {Em_Enum} gem.d3Scene.ECursorEvent.eCursorEvent_Up - cursor has been released(touch up)
		 */
		gem.d3Scene.ECursorEvent.eCursorEvent_Up = Module.CursorEvent.eCursorEvent_Up;
	}
}
/**
 * @class gem.d3Scene.EHighlightOptions 
 * @description Landmarks highlight display options
 * @hideconstructor
 */
gem.d3Scene.EHighlightOptions = class THighlightOptions {
	static initializeData() {
		/**
		 *  @property {Em_Enum} gem.d3Scene.EHighlightOptions .EHO_ShowLandmark - Shows landmark icon & text ( default )
		 */
		gem.d3Scene.EHighlightOptions.EHO_ShowLandmark = Module.THighlightOptions.EHO_ShowLandmark;
		/**
		 *  @property {Em_Enum} gem.d3Scene.EHighlightOptions .EHO_ShowContour - Shows landmark impact area contour ( when available )
		 */
		gem.d3Scene.EHighlightOptions.EHO_ShowContour = Module.THighlightOptions.EHO_ShowContour;
		/**
		 *  @property {Em_Enum} gem.d3Scene.EHighlightOptions .EHO_Group -  Groups landmarks.This option is available only in conjunction with EHO_ShowLandmark
		 */
		gem.d3Scene.EHighlightOptions.EHO_Group = Module.THighlightOptions.EHO_Group;
		/**
		 * @property {Em_Enum} gem.d3Scene.EHighlightOptions .EHO_Overlap - Overlap highlight over existing map data. This option is available only in conjunction with EHO_ShowLandmark
		 */
		gem.d3Scene.EHighlightOptions.EHO_Overlap = Module.THighlightOptions.EHO_Overlap;
		/**
		 *  @property {Em_Enum} gem.d3Scene.EHighlightOptions .EHO_NoFading - Disable highlight fading in / out. This option is available only in conjunction with EHO_ShowLandmark
		 */
		gem.d3Scene.EHighlightOptions.EHO_NoFading = Module.THighlightOptions.EHO_NoFading;
	}
}
/**
 * @class gem.routesAndNavigation.ERoadType
 * @description Road Type
 * @hideconstructor
 */
gem.routesAndNavigation.ERoadType = class TRoadType {
	static initializeData() {
		/**
		 * @property {Em_Enum} gem.routesAndNavigation.ERoadType.EMotorways -  Motorway
		 */
		gem.routesAndNavigation.ERoadType.EMotorways = Module.TRoadType.EMotorways;
		/**
		 * @property {Em_Enum} gem.routesAndNavigation.ERoadType.EStateRoad -  State Road
		 */
		gem.routesAndNavigation.ERoadType.EStateRoad = Module.TRoadType.EStateRoad;
		/**
		 *@property {Em_Enum} gem.routesAndNavigation.ERoadType.ERoad -  Road 
		 */
		gem.routesAndNavigation.ERoadType.ERoad = Module.TRoadType.ERoad;
		/**
		 * @property {Em_Enum} gem.routesAndNavigation.ERoadType.EStreet -  Street
		 */
		gem.routesAndNavigation.ERoadType.EStreet = Module.TRoadType.EStreet;
		/**
		 *@property {Em_Enum} gem.routesAndNavigation.ERoadType.ECycleway -  Cycleway
		 */
		gem.routesAndNavigation.ERoadType.ECycleway = Module.TRoadType.ECycleway;
		/**
		 *@property {Em_Enum} gem.routesAndNavigation.ERoadType.EPath -  Path
		 */
		gem.routesAndNavigation.ERoadType.EPath = Module.TRoadType.EPath;
		/**
		 *@property {Em_Enum} gem.routesAndNavigation.ERoadType.ESingleTrack -  track
		 */
		gem.routesAndNavigation.ERoadType.ESingleTrack = Module.TRoadType.ESingleTrack;
	}
}
/**
 * @class gem.routesAndNavigation.ESurfaceType
 * @description Surface Type
 * @hideconstructor
 */
gem.routesAndNavigation.ESurfaceType = class TSurfaceType {
	static initializeData() {
		/**
		 *@property {Em_Enum} gem.routesAndNavigation.ESurfaceType.EAsphalt - Asphalt
		 */
		gem.routesAndNavigation.ESurfaceType.EAsphalt = Module.TSurfaceType.EAsphalt;
		/**
		 *@property {Em_Enum} gem.routesAndNavigation.ESurfaceType.EPaved - Paved
		 */
		gem.routesAndNavigation.ESurfaceType.EPaved = Module.TSurfaceType.EPaved;
		/**
		 *@property {Em_Enum} gem.routesAndNavigation.ESurfaceType.EUnpaved - Unpaved
		 */
		gem.routesAndNavigation.ESurfaceType.EUnpaved = Module.TSurfaceType.EUnpaved;
		/**
		 * @property {Em_Enum} gem.routesAndNavigation.ESurfaceType.EUnknown - Unknown
		 */
		gem.routesAndNavigation.ESurfaceType.EUnknown = Module.TSurfaceType.EUnknown;
	}
}
/**
 * @class gem.content.EContentType
 * @description Content Type
 * @hideconstructor
 * @property {Em_Enum} gem.content.EContentType.ECT_ViewStyleHighRes - View styles for high dpi displays ( e.g. mobile phones )
 * @property {Em_Enum} gem.content.EContentType.ECT_ViewStyleLowRes - View styles for low dpi displays ( e.g. desktop monitors )
 * @property {Em_Enum} gem.content.EContentType.ECT_RoadMap - Road Map
 */
gem.content.EContentType = class TContentType {
	static ECT_ViewStyleHighRes() {
		return gem.content.EContentType.ECT_ViewStyleHighRes;
	}
	static ECT_ViewStyleLowRes() {
		return Module.TContentType.ECT_ViewStyleLowRes;
	}
	static ECT_RoadMap() {
		return Module.TContentType.ECT_RoadMap;
	}
	static initializeData() {
		gem.content.EContentType.ECT_ViewStyleHighRes = Module.TContentType.ECT_ViewStyleHighRes;
		gem.content.EContentType.ECT_ViewStyleLowRes = Module.TContentType.ECT_ViewStyleLowRes;
		gem.content.EContentType.ECT_RoadMap = Module.TContentType.ECT_RoadMap;
	}
}
/**
 * @class gem.routesAndNavigation.ERouteResultDetails
 * @description Routing result details
 * @hideconstructor
 */
gem.routesAndNavigation.ERouteResultDetails = class ERouteResultDetails {
	static ERD_Full() {
		return gem.routesAndNavigation.ERouteResultDetails.ERD_Full;
	}
	static ERD_TimeDistance() {
		return gem.routesAndNavigation.ERouteResultDetails.ERD_TimeDistance;
	}
	static ERD_Path() {
		return gem.routesAndNavigation.ERouteResultDetails.ERD_Path;
	}
	static initializeData() {
		/**
		 *@property {Em_Enum} gem.routesAndNavigation.ERouteResultDetails.ERD_Full - Path and guidance
		 */
		gem.routesAndNavigation.ERouteResultDetails.ERD_Full = Module.TResultDetails.ERD_Full;
		/**
		 * @property {Em_Enum} gem.routesAndNavigation.ERouteResultDetails.ERD_TimeDistance -  Path time / distance only
		 */
		gem.routesAndNavigation.ERouteResultDetails.ERD_TimeDistance = Module.TResultDetails.ERD_TimeDistance;
		/**
		 *@property {Em_Enum} gem.routesAndNavigation.ERouteResultDetails.ERD_Path -  Path time / distance and geometry
		 */
		gem.routesAndNavigation.ERouteResultDetails.ERD_Path = Module.TResultDetails.ERD_Path;
	}
}

/**
 * @class gem.routesAndNavigation.ERoutePathAlgorithm
 * @description Path calculation algorithm
 * @hideconstructor
 */
gem.routesAndNavigation.ERoutePathAlgorithm = class ERoutePathAlgorithm {
	static EPA_ME() {
		return gem.routesAndNavigation.ERoutePathAlgorithm.EPA_ME;
	}
	static EPA_ExternalCH() {
		return gem.routesAndNavigation.ERoutePathAlgorithm.EPA_ExternalCH;
	}
	static initializeData() {
		/**
		 * @property {Em_Enum} gem.routesAndNavigation.ERoutePathAlgorithm.EPA_ME - Path calculation algorithm - Service default - Magic Earth routing algorithm
		 */
		gem.routesAndNavigation.ERoutePathAlgorithm.EPA_ME = Module.TPathAlgorithm.EPA_ME;
		/**
		 *@property {Em_Enum} gem.routesAndNavigation.ERoutePathAlgorithm.EPA_ExternalCH - Path calculation algorithm - External CH routing algorithm
		 */
		gem.routesAndNavigation.ERoutePathAlgorithm.EPA_ExternalCH = Module.TPathAlgorithm.EPA_ExternalCH;
	}
}

/**
 * @class gem.routesAndNavigation.EBikeProfile
 * @hideconstructor
 * @property {Em_Enum} gem.routesAndNavigation.EBikeProfile.BP_Road - Bike profile - road
 * @property {Em_Enum} gem.routesAndNavigation.EBikeProfile.BP_Cross - Bike profile - cross
 * @property {Em_Enum} gem.routesAndNavigation.EBikeProfile.BP_City -  Bike profile - city
 * @property {Em_Enum} gem.routesAndNavigation.EBikeProfile.BP_Mountain - Bike profile - mountain
 */
gem.routesAndNavigation.EBikeProfile = class EBikeProfile {
	static BP_Road() {
		return gem.routesAndNavigation.EBikeProfile.BP_Road;
	}
	static BP_Cross() {
		return gem.routesAndNavigation.EBikeProfile.BP_Cross;
	}
	static BP_City() {
		return gem.routesAndNavigation.EBikeProfile.BP_City;
	}
	static BP_Mountain() {
		return gem.routesAndNavigation.EBikeProfile.BP_Mountain;
	}
	static initializeData() {
		gem.routesAndNavigation.EBikeProfile.BP_Road = Module.EBikeProfile.BP_Road;
		gem.routesAndNavigation.EBikeProfile.BP_Cross = Module.EBikeProfile.BP_Cross;
		gem.routesAndNavigation.EBikeProfile.BP_City = Module.EBikeProfile.BP_City;
		gem.routesAndNavigation.EBikeProfile.BP_Mountain = Module.EBikeProfile.BP_Mountain;
	}
}

/**
 * @class gem.routesAndNavigation.EEBikeType
 * @description eBike type
 * @hideconstructor
 * @property {Em_Enum} gem.routesAndNavigation.EEBikeType.EBP_None - eBike profile - none
 * @property {Em_Enum} gem.routesAndNavigation.EEBikeType.EBP_Pedelec - eBike profile - pedelec
 * @property {Em_Enum} gem.routesAndNavigation.EEBikeType.EBP_PowerOnDemand -  eBike profile - power-on-demand
 * @property {Em_Enum} gem.routesAndNavigation.EEBikeType.EBP_SPedelec -  eBike type S-pedelec
 */
gem.routesAndNavigation.EEBikeType = class EEBikeType {
	static EBP_None() {
		return gem.routesAndNavigation.EEBikeType.EBP_None;
	}
	static EBP_Pedelec() {
		return gem.routesAndNavigation.EEBikeType.EBP_Pedelec;
	}
	static EBP_PowerOnDemand() {
		return gem.routesAndNavigation.EEBikeType.EBP_PowerOnDemand;
	}
	static EBP_SPedelec() {
		return gem.routesAndNavigation.EEBikeType.EBP_SPedelec;
	}
	static initializeData() {
		gem.routesAndNavigation.EEBikeType.EBP_None = Module.EEBikeType.EBP_None;
		gem.routesAndNavigation.EEBikeType.EBP_Pedelec = Module.EEBikeType.EBP_Pedelec;
		gem.routesAndNavigation.EEBikeType.EBP_PowerOnDemand = Module.EEBikeType.EBP_PowerOnDemand;
		gem.routesAndNavigation.EEBikeType.EBP_SPedelec = Module.EEBikeType.EBP_SPedelec;
	}
}
/**
 * @class gem.ETouchGestures
 * @description A class that represents different touch gestures.
 *
 * This class provides properties to get the different touch gestures.
 *
 * @property {object} TG_OnTouch
 * Represents a single touch gesture.
 *
 * @property {object} TG_OnLongDown
 * Represents a long press gesture.
 *
 * @property {object} TG_OnDoubleTouch
 * Represents a double touch gesture.
 *
 * @property {object} TG_OnTwoPointersTouch
 * Represents a two pointers touch gesture.
 *
 * @property {object} TG_OnTwoPointersDoubleTouch
 * Represents a two pointers double touch gesture.
 *
 * @property {object} TG_OnMove
 * Represents a move gesture.
 *
 * @property {object} TG_OnTouchMove
 * Represents a touch move gesture.
 *
 * @property {object} TG_OnSwipe
 * Represents a swipe gesture.
 *
 * @property {object} TG_OnPinchSwipe
 * Represents a pinch swipe gesture.
 *
 * @property {object} TG_OnPinch
 * Represents a pinch gesture.
 *
 * @property {object} TG_OnRotate
 * Represents a rotate gesture.
 *
 * @property {object} TG_OnShove
 * Represents a shove gesture.
 *
 * @property {object} TG_OnTouchPinch
 * Represents a touch pinch gesture.
 *
 * @property {object} TG_OnTouchShove
 * Represents a touch shove gesture.
 */
gem.ETouchGestures = class ETouchGestures {
	static TG_OnTouch() {
		return gem.ETouchGestures.TG_OnTouch;
	}
	static TG_OnLongDown() {
		return gem.ETouchGestures.TG_OnLongDown;
	}
	static TG_OnDoubleTouch() {
		return gem.ETouchGestures.TG_OnDoubleTouch;
	}
	static TG_OnTwoPointersTouch() {
		return gem.ETouchGestures.TG_OnTwoPointersTouch;
	}
	static TG_OnTwoPointersDoubleTouch() {
		return gem.ETouchGestures.TG_OnTwoPointersDoubleTouch;
	}
	static TG_OnMove() {
		return gem.ETouchGestures.TG_OnMove;
	}
	static TG_OnTouchMove() {
		return gem.ETouchGestures.TG_OnTouchMove;
	}
	static TG_OnSwipe() {
		return gem.ETouchGestures.TG_OnSwipe;
	}
	static TG_OnPinchSwipe() {
		return gem.ETouchGestures.TG_OnPinchSwipe;
	}
	static TG_OnPinch() {
		return gem.ETouchGestures.TG_OnPinch;
	}
	static TG_OnRotate() {
		return gem.ETouchGestures.TG_OnRotate;
	}
	static TG_OnShove() {
		return gem.ETouchGestures.TG_OnShove;
	}
	static TG_OnTouchPinch() {
		return gem.ETouchGestures.TG_OnTouchPinch;
	}
	static TG_OnTouchShove() {
		return gem.ETouchGestures.TG_OnTouchShove;
	}

	static initializeData() {
		gem.ETouchGestures.TG_OnTouch = Module.ETouchGestures.TG_OnTouch;
		gem.ETouchGestures.TG_OnLongDown = Module.ETouchGestures.TG_OnLongDown;
		gem.ETouchGestures.TG_OnDoubleTouch = Module.ETouchGestures.TG_OnDoubleTouch;
		gem.ETouchGestures.TG_OnTwoPointersTouch = Module.ETouchGestures.TG_OnTwoPointersTouch;
		gem.ETouchGestures.TG_OnTwoPointersDoubleTouch = Module.ETouchGestures.TG_OnTwoPointersDoubleTouch;
		gem.ETouchGestures.TG_OnMove = Module.ETouchGestures.TG_OnMove;
		gem.ETouchGestures.TG_OnTouchMove = Module.ETouchGestures.TG_OnTouchMove;
		gem.ETouchGestures.TG_OnSwipe = Module.ETouchGestures.TG_OnSwipe;
		gem.ETouchGestures.TG_OnPinchSwipe = Module.ETouchGestures.TG_OnPinchSwipe;
		gem.ETouchGestures.TG_OnPinch = Module.ETouchGestures.TG_OnPinch;
		gem.ETouchGestures.TG_OnRotate = Module.ETouchGestures.TG_OnRotate;
		gem.ETouchGestures.TG_OnShove = Module.ETouchGestures.TG_OnShove;
		gem.ETouchGestures.TG_OnTouchPinch = Module.ETouchGestures.TG_OnTouchPinch;
		gem.ETouchGestures.TG_OnTouchShove = Module.ETouchGestures.TG_OnTouchShove;
	}
}
/**
 * @class gem.core.ExternalInfo
 * @description Wikipedia info. Required to get wikipedia info for Landmarks.
 * @hideconstructor
 */
gem.core.ExternalInfo = class ExternalInfo extends Em_Object {
	constructor(object) {
		super(new Module.ExternalInfo())
	}
	cancelWikiInfo() {
		this.m_object.cancelWikiInfo();
	}
}

/**
 * @enum {number}
 * @description Route display options.
 */
const ERouteRenderOptions = {
	/** 
	 * Route is main route in collection (i.e., it overlaps all other siblings and has different render settings).
	 */
	RRS_Main: 0x1,
	/** 
	 * Display traffic status over the route. Default is true.
	 */
	RRS_ShowTraffic: 0x2,
	/** 
	 * Display the turn arrows associated with route guidance. Default is true.
	 */
	RRS_ShowTurnArrows: 0x4,
	/** 
	 * Display the route waypoints. Default is true.
	 */
	RRS_ShowWaypoints: 0x8,
	/** 
	 * Enable route highlights. Default is true.
	 * Public routes will render segments in color for bus, public & car routes; pedestrian parts are rendered differently.
	 */
	RRS_ShowHighlights: 0x10,
	/** 
	 * Display user waypoints images.
	 * If enabled, user landmark image will be displayed instead of standard pin image. Default is false.
	 */
	RRS_ShowUserImage: 0x20,
	/** 
	 * Display route direction arrows.
	 * If enabled, the direction arrows will be rendered over the route.
	 */
	RRS_ShowDirectionArrows: 0x40
  };
gem.d3Scene.RouteRenderSettings  = class RouteRenderSettings {
	constructor(routeRenderSettings) {
	  this.options = routeRenderSettings?.options ?? [ERouteRenderOptions.RRS_ShowTraffic, ERouteRenderOptions.RRS_ShowTurnArrows, ERouteRenderOptions.RRS_ShowWaypoints, ERouteRenderOptions.RRS_ShowHighlights];
	  this.innerColor = routeRenderSettings?.innerColor ?? { r: 0, g: 0, b: 0, a: 0 };
	  this.outerColor = routeRenderSettings?.outerColor ?? { r: 0, g: 0, b: 0, a: 0 };
	  this.innerSz = routeRenderSettings?.innerSz ?? -1;
	  this.outerSz = routeRenderSettings?.outerSz ?? 0;
	  this.traveledInnerColor = routeRenderSettings?.traveledInnerColor ?? { r: 0, g: 0, b: 0, a: 0 };
	  this.turnArrowInnerColor = routeRenderSettings?.turnArrowInnerColor ?? { r: 0, g: 0, b: 0, a: 0 };
	  this.turnArrowOuterColor = routeRenderSettings?.turnArrowOuterColor ?? { r: 0, g: 0, b: 0, a: 0 };
	  this.turnArrowInnerSz = routeRenderSettings?.turnArrowInnerSz ?? -1;
	  this.turnArrowOuterSz = routeRenderSettings?.turnArrowOuterSz ?? 0;
	}
  }
/**View class
 * @class gem.d3Scene.MapView
 * @augments Em_Object
 */
gem.d3Scene.MapView = class MapView extends Em_Object {
	/**
	 * Get the zoom level.
	 * @returns {number} The zoom value.
	 */
	getZoomLevel() {
		return Module.View.getZoomLevel(this.m_object);
	}
	/**
	 * Get Maximum value for zoom level.
	 * @returns {number} The maximum zoom value.
	 */
	getMaxZoomLevel() {
		return Module.View.getMaxZoomLevel(this.m_object);
	}
	/**
	 * Set zoom level
	 * @param {number} zoomLevel - The zoom value. 
	 */
	setZoomLevel(zoomLevel) {
		Module.View.setZoomLevel(this.m_object, zoomLevel);
	}
	/** Set zoom level, focusing in a specified screen point
	 * @param {number} zoomLevel - The zoom value.
	 * @param {number}  durationMs - duration The animation duration in milliseconds (0 means no animation)
	 * @param {gem.core.Xy} positionInScreen Screen coordinates under which map should keep position.
	 */
	setZoomLevelOnPoint(zoomLevel, durationMs, positionInScreen) {
		Module.View.setZoomLevelOnPoint(this.m_object, zoomLevel, durationMs, positionInScreen);
	}
	/** Center on a specified coordinates
	 * 	@param {gem.core.Coordinates} coordinates
	 *  @param {number} durationMs
	 *  @param {callback} callbackFunction
	 *  @param {gem.core.iXy} iXy
	 *  @param {number} zoomLevel - zoom level 0 - max zoom level. See getMaxZoomLevel()
	 */
	centerOnCoordinates(coordinates, durationMs, callbackFunction, iXy, zoomLevel) {
		let viewRect = this.getViewport();
		let xy;
		if (iXy === undefined)
			xy = {
				x: viewRect.x + viewRect.width / 2,
				y: viewRect.y + viewRect.height / 2
			};
		else
			xy = iXy;
		zoomLevel = zoomLevel === undefined ? 15 : zoomLevel;
		Module.View.centerOnCoordinates(this.m_object, coordinates, durationMs === undefined ? 0 : durationMs, xy, callbackFunction, zoomLevel === undefined ? -1 : zoomLevel);
	}
	/**  Set the map north-up oriented
	 * @param {number} durationMs - Animation duration in miliseconds. 0 - for instant north-up.
	 */
	alignNorthUp(durationMs) {
		Module.View.alignNorthUp(this.m_object, durationMs);
	}
	/** Convert a screen coordinate to a WGS84 coordinate
	 * @param {gem.core.Xy} screenCoordinates - Screen coordinates
	 * @returns {gem.core.Coordinates} - WGS coordinates
	 */
	transformScreenToWgs(screenCoordinates) {
		return Module.View.transformScreenToWgs(this.m_object, screenCoordinates);
	}

	/**  Convert a WGS84 coordinate to a screen coordinate
	 * @param {gem.core.Coordinates} wgsCoordinates - wgs Coordinates
	 * @returns {gem.core.Xy} - Screen coordinates
	 */
	transformWgsToScreen(wgsCoordinates) {
		return Module.View.transformWgsToScreen(this.m_object, wgsCoordinates);
	}
	/** Center the on the given WGS area specified by top left wgs coordinate and bottomright coordinate
	 * @param {gem.core.Coordinates} coordinatesTopLeft - top left coordinates
	 * @param {gem.core.Coordinates} coordinatesBottomRight - bottom right coordinates
	 * @param {number} durationMs - fly animation duration (milliseconds) 
	 * @param {gem.core.RectangleFloat} rectangleFloat - custom view rectangle
	 * @param {callback} callbackfunction
	 * @param {number} zoomLevel - zoom level 0 - max zoom level. See getMaxZoomLevel()
	 */
	centerOnArea(coordinatesTopLeft, coordinatesBottomRight, durationMs, rectangleFloat, callbackfunction, zoomLevel) {
		let viewRect = this.getViewport();
		let rect;
		if (rectangleFloat === undefined)
			rect = viewRect;
		else
			rect = rectangleFloat;
		if (durationMs === undefined)
			Module.View.centerOnArea(this.m_object, coordinatesTopLeft, coordinatesBottomRight, -1, rect, callbackfunction, zoomLevel === undefined ? -1 : zoomLevel);
		else
			Module.View.centerOnArea(this.m_object, coordinatesTopLeft, coordinatesBottomRight, durationMs, rect, callbackfunction, zoomLevel === undefined ? -1 : zoomLevel);
	}
	/** Resize View
	 * @param {gem.core.RectangleFloat} rectangular - The new size as a rectangle.Values needs to be normalized from the screen size(0->1).
	 */
	resizeView(rectangular) {
		Module.View.resizeView(this.m_object, rectangular);
	}
	/** set tilt angle
	 * @param {number} angle - tilt angle
	 */
	setTilt(angle) {
		Module.View.setTilt(this.m_object, angle);
	}
	/** get tilt angle
	 * @returns {number}  - tilt angle
	 */
	getTilt() {
		return Module.View.getTilt(this.m_object);
	}
	/**  Retrieve, in the given parameter, the list of landmarks under the cursor location.
	 * @param {gem.core.LandmarkList} resultedLandmarks  - resulted Landmarks
	 */
	cursorSelectionLandmarks(resultedLandmarks) {
		Module.View.cursorSelectionLandmarks(this.m_object, resultedLandmarks);
	}

	/**  Set cursor position at a given position in the screen
	 * @param {gem.core.Xy} screenCoordinates  - screen coordinates
	 */
	setCursorScreenPosition(screenCoordinates) {
		Module.View.setCursorScreenPosition(this.m_object, screenCoordinates);
	}
	/** Get current cursor position as WGS Coordinates
	 * @returns {gem.core.Xy}  - WGS Coordinates
	 */
	getCursorPositionWGS() {
		return Module.View.getCursorPositionWGS(this.m_object);
	}
	/**
	 * set the style by id
	 * @param {number} nId 
	 */
	setMapStyle(nId) {
		Module.View.setMapStyle(nId);
	}
	getMapStyle() {
		return Module.View.getMapStyle(this.m_object);
	}
	getLayersIdsAt(screenPosition) {
		return Module.View.getLayersIdsAt(this.m_object, screenPosition);
	}
	/**
	 * 
	 * @param {string} jsonBuffer - style buffer as a string
	 */
	updateCurrentStyleFromJson(jsonBuffer) {
		Module.View.updateCurrentStyleFromJson(this.m_object, jsonBuffer);
	}

	/**
	 * 
	 * @param {object} styleBuffer 
	 */
	updateCurrentStyleFromStyleBuffer(styleBuffer) {
		Module.View.updateCurrentStyleFromStyleBuffer(this.m_object, styleBuffer);
	}
	/**
	 * Toogle map perspective. Predefined tilt values switch
	 */
	toggleMapPerspective() {
		Module.View.toggleMapPerspective(this.m_object);
	}
	/**
	 * Start following the current position.
	 * @param {number} maxZoomLevel - maximum zoom level
	 * @param {number} maxAngle - maximum angle
	 */
	startFollowingPosition(maxZoomLevel, maxAngle) {
		Module.View.startFollowingPosition(this.m_object, maxZoomLevel === undefined ? -1 : maxZoomLevel, maxAngle === undefined ? 60 : maxAngle);
	}
	/**
	 * 
	 * @param {gem.d3Scene.EMarkerType} dataType - type of the source
	 * @param {string} name - name given to the source
	 * @param {gem.d3Scene.MarkerCollectionDisplaySettings} markerCollectionDisplaySettings - display settings
	 * @returns {number} - generated source id
	 */
	createSource(dataType, name, markerCollectionDisplaySettings) {
		if (markerCollectionDisplaySettings === undefined)
			markerCollectionDisplaySettings = new gem.d3Scene.MarkerCollectionDisplaySettings();
		return new gem.d3Scene.MarkerCollection(Module.View.createSource(this.m_object, dataType, name, markerCollectionDisplaySettings.getRawPointer()));
	}

	/**
	 * @callback callbackEdit
	 * @param {gem.d3Scene.ECursorEvent} event - event type
	 * @param {number} pointId - point Id
	 * @param {gem.core.Coordinates}  coordinates - WGS Coordinates of the cursor. 
	 * @param {gem.d3Scene.MarkerRef} parentVector - parent vector
	 */
	/**
	 * 
	 * @param {callbackEdit} callback 
	 */
	enableEdit(callback) {
		var vectorConstructor = function (object) {
			return new gem.d3Scene.Marker(new Module.Marker(object));
		};
		Module.View.enableEdit(this.m_object, callback, vectorConstructor);
	}
	/**
	 * Disable Edit Mode
	 */
	disableEdit() {
		Module.View.disableEdit(this.m_object);
	}
	/**
	 * Center on Route Instruction
	 * @param {gem.routesAndNavigation.RouteInstruction} routeInstruction 
	 * @param {gem.core.screenCoordinates} [screenCoordinates] - screen coordinates, where to center
	 * @param {number} [zoomLevel] - zoom level
	 */
	centerOnRouteInstruction(routeInstruction, screenCoordinates, zoomLevel) {
		let viewRect = this.getViewport();
		let xy;
		if (screenCoordinates === undefined)
			xy = {
				x: viewRect.x + viewRect.width / 2,
				y: viewRect.y + viewRect.width / 2
			};
		else
			xy = screenCoordinates;
		Module.View.centerOnRouteInstruction(this.m_object, routeInstruction.getRawPointer(), xy, zoomLevel === undefined ? -1 : zoomLevel);
	}

	/**
	 * Stop Following position
	 */
	stopFollowingPosition() {
		Module.View.stopFollowingPosition(this.m_object);
	}

	//set map style by store content item. Get the list of available styles with gem.content.getLocalContentList(gem.content.EContentType.ECT_ViewStyleLowRes);
	setMapStyleByStoreItem(contentStoreItem) {
		Module.View.setMapStyleByStoreItem(this.m_object, contentStoreItem);
	}
	/**
	 * Start Orbit Mode
	 */
	startOrbitCamera() {
		Module.View.startOrbitCamera();
	}
	/**
	 * Stop Orbit Mode
	 */
	stopOrbitCamera() {
		Module.View.stopOrbitCamera();
	}
	/**
	 * Capture map screen
	 * @returns {Uint8Array} - screen buffer
	 */
	captureMapScreen() {
		return Module.View.captureMapScreen(this.m_object);
	}
	/**
	 * Capture map screen
	 * @returns {gem.d3Scene.BitmapContainer}
	 */
	captureMapScreenAsBitmapContainer() {
		return Module.View.captureMapScreenAsOffBitmap(this.m_object);
	}
	/**
	 * Check if is it in follow position mode
	 * @returns {boolean}
	 */
	isFollowingPosition() {
		return Module.View.isFollowingPosition(this.m_object);
	}
	/**Enable the default highlight service for the given landmark list.
	 * 
	 * @param {gem.core.LandmarkList} resultedLandmarks - landmark list on which to apply highlights 
	 * @param {gem.core.ColorInfo} colorInfo - color do be used
	 * @param {gem.d3Scene.EHighlightOptions } options - options to be used for the landmark. Can be combined with | operator
	 * @param {gem.d3Scene.EUsableIcons } usableIcons - (optional) Icon to replace with 
	 */
	activateHighlight(resultedLandmarks, colorInfo, options, usableIcons) {
		if (usableIcons === undefined)
			return Module.View.activateHighlight(resultedLandmarks.getRawPointer(), this.m_object, colorInfo, options, -1);
		else
			return Module.View.activateHighlight(resultedLandmarks.getRawPointer(), this.m_object, colorInfo, options, usableIcons);
	}
	/**
	 *  Disable the highlight service.
	 */
	deactivateHighlight() {
		Module.View.deactivateHighlight(this.m_object);
	}
	/**
	 * Add GeoJson as custom marker in the MapView
	 * @param {string} geojsonbuffer - Actual Geo Json string buffer.
	 * @param {string} name - name for the added source(s)
	 * @param {gem.sourceslist} sourceslist - output list of created vector sources
	 * @param {gem.d3Scene.ExternalRenderer} externalRenderer - optional. external renderer. If not used, the internal rendering will be used to display source data.
	 * @param {number} [maxGroupingLevel = 13] - maximum map level to apply grouping
	 */
	addGeoJsonAsCustomMarkers(geojsonbuffer, name, sourceslist, externalRenderer, maxGroupingLevel = 13) {
		var constructorFunc = function (object) {
			return object;
		};
		if (externalRenderer !== undefined)
			Module.View.addGeoJsonAsCustomVector(this.m_object, geojsonbuffer, name, sourceslist.getRawPointer(), externalRenderer.callback.bind(externalRenderer), true, constructorFunc, maxGroupingLevel);
		else
			Module.View.addGeoJsonAsCustomVector(this.m_object, geojsonbuffer, name, sourceslist.getRawPointer(), null, false, constructorFunc, maxGroupingLevel);
	}

	/**
	 * Add Store Locations list as custom markers in the MapView
	 * @param {gem.core.StoreLocationList} storeList - Store List to be added to the view
	 * @param {string} name - Store list name
	 * @param {gem.sourceslist} sourceslist - output list of created vector sources
	 * @param {gem.d3Scene.ExternalRenderer} externalRenderer - optional. external renderer. If not used, the internal rendering will be used to display source data.
	 * @param {number} storeId - Store Id
	 * @param {number} [nMaxGroupingLevel = 13] - Maximum level on which grouping can occur
	 */
	addStoreListAsCustomMarkers(storeList, name, sourceslist, externalRenderer, storeId, nMaxGroupingLevel) {
		var constructorFunc = function (object) {
			return new gem.d3Scene.MarkerRef(object);
		};
		if (externalRenderer !== undefined) {
			externalRenderer.mStoreList = new Map();
			externalRenderer.mStoreId = storeId;
			for (let i = 0; i < storeList.size(); i++) {
				let it = storeList.get(i);
				if (it !== undefined) {
					it.mId = it.getId();
					externalRenderer.mStoreList[it.mId] = it;
				}
			}
			Module.View.addStoreListAsCustomVector(this.m_object, storeList.getRawPointer(), name, sourceslist.getRawPointer(), externalRenderer.callback.bind(externalRenderer), true, constructorFunc, nMaxGroupingLevel === undefined ? 13 : nMaxGroupingLevel);
		} else
			Module.View.addStoreListAsCustomVector(this.m_object, storeList.getRawPointer(), name, sourceslist.getRawPointer(), null, false, constructorFunc, 20);
	}

	/**
	 * Register click event listener for landmarks.
	 * @param {gem.core.LandmarkList} resultedLandmarks - used for adding the clicked landmarks
	 * @param {callback} callbackfunction -  Click event callback
	 */
	registerLandmarkClickedEvent(resultedLandmarks, callbackfunction) {
		Module.View.registerLandmarkClickedEvent(this.m_object, resultedLandmarks.getRawPointer(), callbackfunction);
	}
	/**
	 * @callback callbackMarkers
	 * @param {gem.d3Scene.OverlayItem} - markerlist - please convert to a Marker list wrapper
	 */
	/**
	 * 
	 * @param {callbackMarkers} callbackfunction 
	 */
	registerOverlayItemClickedEvent(callbackfunction) {
		var createLandmark = function (object) {
			return new gem.d3Scene.OverlayItemList(object.clone());
		};
		Module.View.registerMarkerClickedEvent(this.m_object, callbackfunction, createLandmark);
	}
	/**  Add a route in the collection to be shown in the view
	 * @param {gem.Route} route - route that is being added
	 * @param {boolean} bIsMainRoute - if the route should be added as a main route or not.
	 * @param {boolean} bAddRouteLabels - if the route labels should be added or not.
	 * @param {RouteRenderSettings} routeRenderSettings - route render settings
	*/
	showRouteInView(route, bIsMainRoute, bAddRouteLabels,routeRenderSettings) {
		if(routeRenderSettings === undefined)
			routeRenderSettings = new gem.d3Scene.RouteRenderSettings();
		Module.View.showRouteInView(this.m_object, route.getRawPointer(), bIsMainRoute, bAddRouteLabels === undefined ? true : bAddRouteLabels,routeRenderSettings);
	}
	/**  Remove route from view
	 * @param {gem.Route} route - route that is being removed from the view
	 */
	removeRoutefromView(route) {
		Module.View.removeRouteFromView(this.m_object, route.getRawPointer());
	}
	/**
	 * Center on a route( route overview)
	 * @param {gem.routesAndNavigation.Route} route 
	 * @param {number} [durationMs = 0] - duration for animation in ms
	 * @param {gem.core.RectangleFloat} rectFloat - custom view rectangle
	 */
	centerOnRoute(route, durationMs, rectFloat) {
		let rect;
		if (rectFloat === undefined)
			rect = this.getViewport();
		else
			rect = rectFloat;
		Module.View.centerOnRoute(this.m_object, route.getRawPointer(), durationMs === undefined ? 0 : durationMs, rect);
	}
	/**
	 * @param {number} nId - id for the needed source
	 * @returns {gem.d3Scene.MarkerCollectionRef} - source
	 */
	getVectorSourceAt(nId) {
		return new gem.d3Scene.MarkerCollection(nId, this.m_object);
	}
	/**
	 * 
	 * @param {gem.d3Scene.MarkerCollectionRef} pSource - source that should be deleted
	 */
	removeVectorSource(pSource) {
		Module.View.removeSource(this.m_object, pSource.getRawPointer());
	}
	/**
	 * 
	 * @param {*} callback 
	 */
	registerCameraChangeStateCallback(callback) {
		Module.View.registerCameraChangeStateCallback(this.m_object, callback);
	}
	/**
	 * Query store locator
	 * @param {gem.d3Scene.ExternalQueryListener} externalQueryListener 
	 * @param {gem.core.StoreLocationList} storeLocationList 
	 * @param {number} storeId - store ID
	 * @param {boolean} bFullData - To query full data, or partial data
	 * @param {gem.core.GemStringList} languageList
	 * @param {gem.d3Scene.RectangleGeographicAreaList} rectangleList - list of regions to query for data
	 */
	queryStoreLocator(externalQueryListener, storeLocationList, storeId, bFullData, languageList, rectangleList) {
		if (languageList === undefined)
			languageList = new gem.core.GemStringList();

		if (rectangleList === undefined)
			rectangleList = this.transformScreenToWgsRectGeoList({
				x: 0,
				y: 0,
				width: 0,
				height: 0
			});
		Module.View.queryStoreLocator(this.m_object, externalQueryListener.getRawPointer(), storeLocationList.getRawPointer(), storeId, languageList.getRawPointer(), bFullData === undefined ? true : bFullData, rectangleList.getRawPointer());
	}
	/**
	 * @param {gem.d3Scene.ExternalQueryListener} externalQueryListener 
	 * @param {number} storeId 
	 * @param {gem.core.StoreLocationLangList} resultConfigurationList 
	 */
	queryStoreLocatorConfiguration(externalQueryListener, storeId, resultConfigurationList) {
		Module.View.queryStoreLocatorConfiguration(this.m_object, externalQueryListener.getRawPointer(), storeId, resultConfigurationList.getRawPointer());
	}
	/**
	 * Enable overlay Item with external Rendering
	 * @param {string} uId 
	 * @param {gem.d3Scene.ExternalRenderer} externalRenderer 
	 */
	enableOverlayItemWithExternalRendering(uId, externalRenderer) {
		var constructorFunc = function (object) {
			return new gem.d3Scene.OverlayItem(new Module.OverlayItem(object));
		};
		Module.View.enableMarkersWithExternalRendering(uId, this.m_object, externalRenderer.callback.bind(externalRenderer), constructorFunc);
	}
	/**
	 * Get notified when style update has finished
	 * @param {*} callbackStyle 
	 */
	registerStyleUpdateFinishedCallback(callbackStyle) {
		Module.View.registerStyleUpdateFinishedCallback(this.m_object, callbackStyle);
	}
	/**
	 * Set area of  view clipping
	 * @param {gem.core.RectangleFloat} area 
	 */
	setClippingArea(area) {
		Module.View.setClippingArea(this.m_object, area);
	}
	/**
	 * Get total Number of Markers Collections
	 * @returns {number}
	 */
	getNumberOfMarkersCollections() {
		return Module.View.getNumberOfSources(this.m_object);
	}
	/**Get View viewport
	 *@returns {gem.core.RectangleInteger} - viewport
	 */
	getViewport() {
		return Module.View.getViewport(this.m_object);
	}
	/**
	 * @param {boolean} bVal 
	 */
	enableCursor(bVal) {
		Module.View.enableCursor(this.m_object, bVal);
	}
	/**
	 * Transform screen coordinates to a list of Geographic Areas (WGS coordinates)
	 * @param {gem.core.RectangleInteger} screenRectangle
	 * @returns {gem.core.RectangleGeographicAreaList} - list of Geographic Areas
	 */
	transformScreenToWgsRectGeoList(screenRectangle) {
		return new gem.core.RectangleGeographicAreaList(Module.View.transformScreenToWgsRectGeoList(this.m_object, screenRectangle));
	}

	/**
	 * @param {bool} bFlag 
	 */
	setNorthFixedFlag(bFlag) {
		//Module.View.setNorthFixedFlag(this.m_object, bFlag);
	}
	/**
	 * @returns {bool}
	 */
	getNorthFixedFlag() {
		//return Module.View.getNorthFixedFlag(this.m_object);
		return null;
	}

	/**
	 * @param {*} callback 
	 */
	registerForNextRenderFinished(callback) {
		Module.View.registerForNextRenderFinished(this.m_object, callback);
	}
	/**
	 * @param {*} callback 
	 */
	registerOnViewRendered(callback) {
		Module.View.registerOnViewRendered(this.m_object, callback);
	}
	/**
	 * @param {*} callback 
	 */
	registerOnTouchEvent(callback) {
		Module.View.registerOnTouchEvent(this.m_object, callback);
	}
	registernOnLongTouchEvent(callback) {
		Module.View.registernOnLongTouchEvent(this.m_object, callback);
	}
	registerRouteClickedEvent(callback, routeResult) {
		Module.View.registerRouteClickedEvent(this.m_object, routeResult.getRawPointer(), callback)
	}
	deregisterRouteClickedEvent() {
		Module.View.deregisterRouteClickedEvent();
	}
	registerHoverLandmarkCallback(callback) {
		let constructorCallback = function (landmarkResult) {
			if (landmarkResult !== undefined) {
				let landmarkWrapped = new gem.core.Landmark(landmarkResult);
				callback(landmarkWrapped);
			} else
				callback(undefined);
		};
		Module.View.registerHoverLandmarkCallback(this.m_object, constructorCallback);
	}
	getHighlightGroupItemIndex(nIndex) {
		return Module.View.getHighlightGroupItemIndex(this.m_object, nIndex);
	}
	setCamera(camera) {
		Module.View.setCamera(this.m_object, camera);
	}
	/**
	 * Center on a route( route overview)
	 * @param {gem.routesAndNavigation.Route} route 
	 * @param {number} distBegin - distance begin
	 * @param {number} distEnd - distance end
	 * @param {number} [durationMs = 0] - duration for animation in ms
	 * @param {gem.core.RectangleFloat} rectFloat - custom view rectangle
	 * 
	 */
	centerOnRoutePortion(route, distBegin, distEnd, durationMs, rectFloat) {
		let rect;
		if (rectFloat === undefined)
			rect = this.getViewport();
		else
			rect = rectFloat;
		Module.View.centerOnRoutePortion(this.m_object, route.getRawPointer(), durationMs === undefined ? 0 : durationMs, rect, distBegin, distEnd);
	}
	getVisibleRouteInterval(route, rectFloat) {
		let rect;
		if (rectFloat === undefined)
			rect = this.getViewport();
		else
			rect = rectFloat;
		return Module.View.getVisibleRouteInterval(this.m_object, route.getRawPointer(), rect);
	}
	getPathsCollection() {
		return Module.View.getPathsCollection(this.m_object);
	}
	/**
	Toggle Touch Gesture
	@param {gem.ETouchGestures} gestures - gestures to be toggled
	@param {boolean} enableFlag - enable or disable
	*/
	enableTouchGestures(gestures, enableFlag) {
		Module.View.enableTouchGestures(this.m_object, gestures, enableFlag);
	}
	/**
	 * Add a landmark store to the view
	 * @param {gem.core.LandmarkStore} landmarkStore - landmark store to be added
	 */
	addLandmarkStore(landmarkStore) {
		Module.View.addLandmarkStore(this.m_object, landmarkStore.getRawPointer());
	}
	/**
	 * Remove a landmark store from the view
	 * @param {gem.core.LandmarkStore} landmarkStore - landmark store to be removed
	 */
	removeLandmarkStore(landmarkStore) {
		Module.View.removeLandmarkStore(this.m_object, landmarkStore.getRawPointer());
	}
	/**
	 * 
	 * @param {number} qualityLevel - 0 = Low Quality / High Performance;1 = Medium Quality; 2 = High Quality/ Low Performance
	 */
	setMapViewDetailsQualityLevel(qualityLevel) {
		Module.View.setMapViewDetailsQualityLevel(this.m_object, qualityLevel);
	}
	/**
	 * Adds a path to the view.
	 * @param {object} path - The path object to add.
	 * @param {{r: number, g: number, b: number, a: number}} borderColor - The RGBA color of the border.
	 * @param {{r: number, g: number, b: number, a: number}} innerColor - The RGBA color of the inner part of the path.
	 * @param {number} [sizeOfBorder=-1] - The size of the border. Defaults to -1 if not provided.
	 * @param {number} [sizeOfInner=-1] - The size of the inner part of the path. Defaults to -1 if not provided.
	 */
	addPath(path, borderColor, innerColor, sizeOfBorder = -1, sizeOfInner = -1) {
		Module.View.addPath(this.m_object, path.getRawPointer(), borderColor, innerColor, sizeOfBorder, sizeOfInner);
	}

	/**
	 * Removes a path from the view.
	 * @param {object} path - The path object to remove.
	 */
	removePath(path) {
		Module.View.removePath(this.m_object, path.getRawPointer());
	}

	/**
	 * Sets the fading of map labels.
	 * @param {boolean} bVal - If true, map labels will fade. If false, they will not.
	 */
	setMapLabelsFading(bVal) {
		Module.View.setMapLabelsFading(this.m_object, bVal);
	}

	/**
	 * Sets the visibility of traffic information on the map.
	 * @param {boolean} bVal - If true, traffic information will be visible. If false, it will not.
	 */
	setTrafficVisibility(bVal) {
		Module.View.setTrafficVisibility(this.m_object, bVal);
	}

	getMapViewMaxZoomRanges() {
		return Module.View.getMapViewMaxZoomRanges();
	}
	getMapViewCurrentZoomRange()
	{
		return Module.View.getMapViewCurrentZoomRange(this.m_object);
	}
}

/**
 * @class gem.core.RectangleGeographicAreaList
 * @hideconstructor
 * @memberof gem.core
 */
gem.core.RectangleGeographicAreaList = class RectangleGeographicAreaList extends Em_Vector {

	constructor(object) {
		if (object)
			super(object);
		else
			super(new Module.RectangleGeographicAreaList());
	}
	/**
	 * @param {number} idx 
	 * @returns {RectangleGeographicArea}
	 */
	get(idx) {
		return Module.RectangleGeographicAreaList.get(this.m_object, idx);
	}
	/**
	 * @param {RectangleGeographicArea} item 
	 */
	push_back(item) {
		Module.RectangleGeographicAreaList.push_back(this.m_object, item);
	}
	/**
	 * @returns {number}
	 */
	size() {
		return Module.RectangleGeographicAreaList.size(this.m_object);
	}
}
/**
 * @namespace gem.places
 * @hideconstructor
 * @memberof gem
 */

gem.places = class places {

}
/**
 * @class gem.places.Search
 * @hideconstructor
 */
gem.places.Search = class search {

	/**
	 * Request to search for nearby Landmarks(POIs)
	 * @param {gem.core.Coordinates} coordinates 
	 * @param {*} callbackfunction 
	 * @param {gem.core.LandmarkList} resultedLandmarks 
	 * @param {gem.places.SearchPreferences} searchPreferences - Search Preferences
	 */
	static searchNearby(coordinates, callbackfunction, resultedLandmarks, searchPreferences) {

		Module.searchNearby(coordinates, callbackfunction, resultedLandmarks.getRawPointer(), (searchPreferences === undefined) ? new Module.SearchPreferences() : searchPreferences.getRawPointer());
	}
	/**
	 * Search for a specific string, in a specified Area(top left / top right coordinates) 
	 * @param {string} searchString 
	 * @param {gem.core.Coordinates} coordinatesTopLeft 
	 * @param {gem.core.Coordinates} coordinatesBottomRight 
	 * @param {*} callbackfunction 
	 * @param {gem.core.LandmarkList} resultedLandmarks
	 * @param {gem.core.Coordinates} pointOfInterest - Point of interest.You can use the cursor positon as input, request it via by call getCursorPositionWGS , on MapView object
	 */
	static searchFor(searchString, coordinatesTopLeft, coordinatesBottomRight, callbackfunction, resultedLandmarks, pointOfInterest) {
		if (pointOfInterest === undefined) {
			pointOfInterest = {
				latitude: (coordinatesTopLeft.latitude + coordinatesBottomRight.latitude) / 2.0,
				longitude: (coordinatesTopLeft.longitude + coordinatesBottomRight.longitude) / 2.0,
				altitude: 0,
				bearing: 0
			};
		}
		Module.searchFor(searchString, coordinatesTopLeft, coordinatesBottomRight, callbackfunction, resultedLandmarks.getRawPointer(), pointOfInterest);
	}
	/**
	 * Search for a specific string,in a specified Area (top left / top right coordinates) with more control over the search preferences.
	 * @param {string} searchString 
	 * @param {gem.core.Coordinates} coordinatesTopLeft 
	 * @param {gem.core.Coordinates} coordinatesBottomRight 
	 * @param {*} callbackfunction 
	 * @param {gem.core.LandmarkList} resultedLandmarks 
	 * @param {gem.places.SearchPreferences} searchPreferences 
	 */
	static searchForWithPreferences(searchString, coordinatesTopLeft, coordinatesBottomRight, callbackfunction, resultedLandmarks, searchPreferences) {
		Module.searchForWithPreferences(searchString, coordinatesTopLeft, coordinatesBottomRight, callbackfunction, resultedLandmarks.getRawPointer(), (searchPreferences === undefined) ? new gem.places.SearchPreferences() : searchPreferences.getRawPointer());
	}
	/**
	 * Search along a route
	 * @param {string} searchString 
	 * @param {*} callbackfunction 
	 * @param {gem.routesAndNavigation.Route} route 
	 * @param {gem.core.LandmarkList} resultedLandmarks 
	 * @param {gem.places.SearchPreferences} searchPreferences 
	 */
	static searchAlongRouteEM(searchString, callbackfunction, route, resultedLandmarks, searchPreferences) {
		Module.searchAlongRouteEM(searchString, callbackfunction, route.getRawPointer(), resultedLandmarks.getRawPointer(), searchPreferences.getRawPointer());
	}
	/**
	 * 
	 * @param {string} searchString - search string
	 * @param {gem.core.EAddressDetailLevel } detailLevel - detail level
	 * @param {gem.core.Landmark} parentLandmark  - parent landmark
	 * @param {*} callbackfunction - callback function
	 * @param {gem.lanmarklist} resultedLandmarks - resulted Landmarks
	 * @param {number} maximumResults - maximum number of results
	 * @param {boolean} allowFuzzyResults - allow fuzzy results
	 * @param {boolean} searchOnlyOnBoard - search only on board
	 */
	static searchByAddress(searchString, detailLevel, parentLandmark, callbackfunction, resultedLandmarks, maximumResults, allowFuzzyResults, searchOnlyOnBoard) {
		Module.searchByAddress(searchString, detailLevel, parentLandmark.getRawPointer(), callbackfunction, resultedLandmarks.getRawPointer(), maximumResults, (allowFuzzyResults === undefined) ? true : allowFuzzyResults, (searchOnlyOnBoard === undefined) ? false : searchOnlyOnBoard);
	}
	/**
	 * 
	 * @param {gem.core.LandmarkCategoryList} landmarkCategoryList
	 * @param {gem.core.Coordinates} coordinates 
	 * @param {*} callback 
	 * @param {gem.core.LandmarkList} resultedLandmarks 
	 * @param {number} [distanceRadiusMeters]
	 */
	static searchPoiCategory(landmarkCategoryList, coordinates, callback, resultedLandmarks, distanceRadiusMeters) {
		Module.searchPoiCategory(landmarkCategoryList.getRawPointer(), coordinates, callback, resultedLandmarks.getRawPointer(), distanceRadiusMeters ? distanceRadiusMeters : -1);
	}
	/**
	 * 
	 * @param {gem.core.Landmark} landmark
	 * @returns {vector<number>}
	 */
	static getNextAddressDetailLevel(landmark) {
		return Module.getNextAddressDetailLevel(landmark.getRawPointer());
	}
	static cancelSearch(resultedLandmarks) {
		Module.cancelSearch(resultedLandmarks.getRawPointer());
	}
}
/**searchPreferences class
 * @class gem.places.SearchPreferences
 * @memberof gem.places
 */
gem.places.SearchPreferences = class SearchPreferences extends Em_Object {
	constructor() {
		super(new Module.SearchPreferences());
	}
	/**
	 *  Set/unset the exact match.
	 * @param {boolean} bVal 
	 */
	setExactMatch(bVal) {
		this.m_object.setExactMatch(bVal);
	}
	/**
	 *  Sets the max number of matches.
	 * @param {number} nMaxMatches 
	 */
	setMaxMatches(nMaxMatches) {
		this.m_object.setMaxMatches(nMaxMatches);
	}
	/**
	 * Enables or disables the search through the addresses.
	 * @param {boolean} bVal 
	 */
	setSearchAddresses(bVal) {
		this.m_object.setSearchAddresses(bVal);
	}
	/**
	 *  Enables or disables the search through map POIs.
	 * @param {boolean} bVal 
	 */
	setSearchMapPOIs(bVal) {
		this.m_object.setSearchMapPOIs(bVal);
	}
	/**
	 * Set the threshold distance for the operation.
	 * This may be used to control the reverse geocoding and search along route lookup area.
	 * @param {*} nThresholdDistance 
	 */
	setThresholdDistance(nThresholdDistance) {
		this.m_object.setThresholdDistance(nThresholdDistance);
	}
	/**
	 * Set the search operation time-out.
	 * @param {number} timeoutMs 
	 */
	setTimeout(timeoutMs) {
		this.m_object.setTimeout(timeoutMs);
	}
	/**
	 *  Set the flag for onboard search.
	 * If this flag is true then the search will be done using only onboard data. 
	 *  By default it is false.
	 * @param {boolean} bVal 
	 */
	setSearchOnlyOnboard(bVal) {
		this.m_object.setSearchOnlyOnboard(bVal);
	}
	/**
	 * Add categories in which to search
	 * @param {gem.core.LandmarkCategoryList} landmarkcategorylist 
	 */
	addLandmarkCategoryVector(landmarkcategorylist) {
		this.m_object.addLandmarkCategoryVector(landmarkcategorylist.getRawPointer());
	}
	/**
	 * Set the references point.
	 * @param {gem.core.Coordinates} coordinates 
	 */
	setReferencePoint(coordinates) {
		this.m_object.setReferencePoint(coordinates);
	}
}

/**Content Manager class
 * @class gem.content.Manager
 * @hideconstructor
 * @memberof gem.content
 */
gem.content.Manager = class manager {
	/**
	 * Request content of a certain type. Async function
	 * @param {gem.content.EContentType} contentType -  content type
	 * @param {gem.content.StoreProgressListener} progressListener - when content is made available, you get notified in this object*/
	static requestContentList(contentType, progressListener) {
		Module.requestContentList(contentType, progressListener.getRawPointer());
	}
	/**
	 * Gets access to the installed content list
	 * @param {gem.content.EContentType} contentType - Content list type
	 * @returns {gem.content.ContentStoreList}
	 */
	static getLocalContentList(contentType) {
		return new gem.content.ContentStoreList(Module.getLocalContentList(contentType));
	}
	/**
	 *  Get generic categories list
	 * @returns {gem.core.LandmarkCategoryList}
	 */
	static getGenericCategories() {
		return new gem.core.LandmarkCategoryList(Module.getGenericCategories());
	}
	/**
	 * Gets access to the store cached content list
	 * @param {gem.content.EContentType} contentType
	 * @returns {gem.content.ContentStoreList} - content store list
	 */
	static getStoreContentList(contentType) {
		return new gem.content.ContentStoreList(Module.getStoreContentList(contentType));
	}
	/**
	 * Gets list of url for a certain service type
	 * @param {number} customUrlServiceId - Service type id
	 * @returns {gem.core.GemStringList} - list of urls
	 */
	static getCustomUrlList(customUrlServiceId) {
		return Module.getCustomUrlList(customUrlServiceId);
	}
	/**
	 * Get List with ids of available services
	 * @returns vector<int>
	 */
	static getServicesIds() {
		return Module.getServicesIds();
	}
	/**
	 * 
	 * @param {number} nId Service id 
	 * @returns {string} - name of service
	 */
	static getServiceName(nId) {
		return Module.getServiceName(nId);
	}
	static getIconFromId(offBitmap, nId) {
		return Module.getIconFromId(offBitmap.getRawPointer(), nId);
	}
	static getImageAspectRatio(nId) {
		return Module.getImageAspectRatio(nId);
	}
}
/**Navigation class
 * @class gem.routesAndNavigation.Navigation
 * @hideconstructor
 * @memberof gem.routesAndNavigation
 */
gem.routesAndNavigation.Navigation = class Navigation {
	/**
	 *  Check if there is an active simulation in progress.
	 * @returns {boolean}
	 * @static
	 */
	static isSimulationActive() {
		return Module.isSimulationActive();
	}

	/**
 * Sets the speed multiplier for the simulation.
 * @param {number} fVal - The new speed multiplier.
 * @static
 */
	static setSpeedMultiplier(fVal) {
		Module.setSpeedMultiplier(fVal);
	}

	/**
	 * Gets the current speed multiplier for the simulation.
	 * @returns {number} The current speed multiplier.
	 * @static
	 */
	static getSpeedMultiplier() {
		return Module.getSpeedMultiplier();
	}

	/**
	 * Gets the current speed of the simulation.
	 * @returns {number} The current speed.
	 * @static
	 */
	static getCurrentSpeed() {
		let speed = Module.getCurrentSpeed();
		if(speed < -990)
			return undefined;
		return speed;
	}

	/**
	 * Creates a new simulation listener.
	 * @returns {Module.SimulationListener} A new simulation listener.
	 * @static
	 */
	static createSimulationListener() {
		return new Module.SimulationListener();
	}
	/**
	 * 
	 * @param {gem.routesAndNavigation.Route} route 
	 * @param {*} progressListener - callback
	 * @param {gem.routesAndNavigation.NavigationListener} navigationListener
	 * @returns {number} - Error code
	 */
	static startNavigation(route, progressListener, navigationListener) {
		return Module.startNavigation(route.getRawPointer(), progressListener, navigationListener.getRawPointer());
	}
	/**
	 * Stop Navigation
	 */
	static stopNavigation() {
		Module.stopNavigation();
	}
	/**
	 * Start Simulation
	 * @param {gem.routesAndNavigation.Route} route 
	 * @param {*} progressListener - callback
	 * @param {gem.routesAndNavigation.NavigationListener} navigationListener 
	 */
	static startSimulation(route, progressListener, navigationListener) {
		Module.startSimulation(route.getRawPointer(), progressListener, navigationListener.getRawPointer())
	}
	/** Get current street name
	 * @returns {string}
	 */
	static getCurrentStreetName() {
		return Module.NavigationInstruction.getCurrentStreetName();
	}
	/**Get the maximum speed limit on the current street in meters per second
	 * @returns {number} - 0 if maximum speed limit is not available
	 */
	static getCurrentStreetSpeedLimit() {
		return Module.NavigationInstruction.getCurrentStreetSpeedLimit();
	}
	/** Get the current navigation instruction id.
	 *@returns {number}
	 */
	static getCurrentInstructionId() {
		return Module.NavigationInstruction.getCurrentInstructionId();
	}
	/**Get remaining travel distance in meters and remaining traveling time in seconds.
	 * @returns {gem.core.TimeAndDistance}
	 */
	static getRemainingTravelTimeDistance() {
		return Module.NavigationInstruction.getRemainingTravelTimeDistance();
	}
	/**Get the distance to the next turn in meters, time in seconds.
	 * @returns {gem.core.TimeAndDistance}
	 */
	static getTimeDistanceToNextTurn() {
		return Module.NavigationInstruction.getTimeDistanceToNextTurn();
	}
	/**Get the textual description for the next turn.
	 *@returns {string}
	 */
	static getNextTurnInstruction() {
		return Module.NavigationInstruction.getNextTurnInstruction();
	}
	/**
	 * Get turn image.
	 * @param {gem.core.BitmapContainer} bitmapContainer -Buffer where to add the image for the turn.
	 */
	static getNextTurnImageInBitmap(bitmapContainer) {
		Module.NavigationInstruction.getNextTurnImageInBitmap(bitmapContainer.getRawPointer());
	}

}
/**Routes Request class
 * @class gem.routesAndNavigation.RoutesRequest
 * @augments Em_Vector
 * @memberof gem.routesAndNavigation
 */
gem.routesAndNavigation.RoutesRequest = class routesrequest extends Em_Vector {
	constructor() {
		super(new Module.Route());
	}
	/**
	 * Add a waypoint, by coordinates, for route calculation.
	 * @param {gem.core.Coordinates} coordinates
	 */
	addWaypoint(coordinates) {
		this.m_object.addWaypoint(coordinates);
	}
	/**
	 *  Set the route type.
	 * @param {gem.routesAndNavigation.ERouteType} routeType 
	 */
	setRouteType(routeType) {
		this.m_object.setRouteType(routeType);
	}
	/**
	 * Sets the avoid unpaved roads flag.
	 * @param {boolean} bValue 
	 */
	setAvoidUnpavedRoads(bValue) {
		this.m_object.setAvoidUnpavedRoads(bValue);
	}
	/**
	 *  Set the avoid motorways flag. Default is false
	 * @param {boolean} bValue 
	 */
	setAvoidMotorways(bValue) {
		this.m_object.setAvoidMotorways(bValue);
	}
	/**
	 * Set the avoid toll roads flag.Default is false
	 * @param {boolean} bValue 
	 */
	setAvoidTollRoads(bValue) {
		this.m_object.setAvoidTollRoads(bValue);
	}
	/**
	 * Set the avoid ferries flag.
	 * @param {boolean} bValue 
	 */
	setAvoidFerries(bValue) {
		this.m_object.setAvoidFerries(bValue);
	}
	/**
	 * Enable/disable traffic information usage for route calculation.Default is false.
	 * @param {boolean} bValue 
	 */
	setAvoidTraffic(bValue) {
		this.m_object.setAvoidTraffic(bValue);
	}
	/**
	 * Set avoid biking hill factor
	 * @param {number} fValue - 0.0 - no avoidance, 1.0 - full avoidance
	 */
	setAvoidBikingHillFactor(fValue) {
		this.m_object.setAvoidBikingHillFactor(fValue);
	}
	/**
	 * Sets the transport mode.
	 * @param {gem.routesAndNavigation.ERouteTransportMode} transportMode 
	 */
	setTransportMode(transportMode) {
		this.m_object.setTransportMode(transportMode);
	}
	/**
	 * Add a waypoint, by landmark, for route calculation.
	 * @param {gem.core.Landmark} landmark 
	 */
	addWaypointFromLandmark(landmark) {
		this.m_object.addWaypointFromLandmark(landmark.getRawPointer());
	}
	/**
	 * 
	 * @param {*} callbackfunction 
	 */
	calculateRoute(callbackfunction) {
		this.m_object.calculateRoute(callbackfunction);
	}

	/**
	 * Enable terrain profile build
	 * @param {boolean} bValue 
	 */
	setBuildTerrainProfile(bValue) {
		this.m_object.setBuildTerrainProfile(bValue);
	}
	/**
	 * Sets the path calculation algorithm
	 * @param {gem.routesAndNavigation.ERoutePathAlgorithm} pathAlgorithm 
	 */
	setPathAlgorithm(pathAlgorithm) {
		this.m_object.setPathAlgorithm(pathAlgorithm);
	}
	/**
	 * Set result details
	 * @param {gem.routesAndNavigation.ERouteResultDetails} resultDetails 
	 */
	setResultDetails(resultDetails) {
		this.m_object.setResultDetails(resultDetails);
	}
	/**
	 * Set Bike Profile
	 * @param {gem.routesAndNavigation.EBikeProfile} bikeProfile - Set the bike profile to be used for the route calculation.
	 */
	setBikeProfile(bikeProfile) {
		this.m_object.setBikeProfile(bikeProfile);
	}
	/**
	 * Set Bike Profile and Electric bike profile
	 * @param {gem.routesAndNavigation.EBikeProfile} bikeProfile
	 * @param {gem.routesAndNavigation.ElectricBikeProfile} electricBikeProfile
	 */
	setBikeProfileWithElectricType(bikeProfile, electricBikeProfile) {
		if (typeof electricBikeProfile.type === 'function') {
			electricBikeProfile.type = electricBikeProfile.type();
		}
		this.m_object.setBikeProfileWithElectricType(bikeProfile, electricBikeProfile);
	}
	/**
	 * Set route ranges list.A non empty range list will generate an isocost range route result
	@param {object} routeranges
	* @param {Array} [routeranges.rangelist] - array of range intervals. ranges units depends on route type(set by setRouteType): gem.routesAndNavigation.ERouteType.ERT_Fastest - seconds,  gem.routesAndNavigation.ERouteType.ERT_Shortest - meters , gem.routesAndNavigation.ERouteType.ERT_Economic - watt-hour( Wh)
	* @param {callbackMarker} [routeranges.quality] - range result quality must a valid integer in 0 - 100 range, 0 = lowest quality, 100 = highest quality
	*/
	setRouteRanges(routeranges) {
		let array = new Module.vector$int$();
		for (let i = 0; i < routeranges.rangelist.length; i++) {
			array.push_back(routeranges.rangelist[i]);
		}
		this.m_object.setRouteRanges(array, routeranges.quality);
	}
	setIgnoreRestrictionsOverTrack(ignore) {
		this.m_object.setIgnoreRestrictionsOverTrack(ignore);
	}
	/**	
	 * @returns {number} - the number of calculated routes.
	 */
	size() {
		return this.m_object.getRoutesListSize();
	}
	/**
	 * 
	 * @param {number} nId
	 * @returns {gem.routesAndNavigation.Route} - Route Object
	 */
	get(nId) {
		return new gem.routesAndNavigation.Route(this.m_object, nId);
	}

	stopRouteCalculation() {
		this.m_object.stopRouteCalculation();
	}
	/**
	 * 
	 * @param {gem.routesAndNavigation.EVProfile} evProfile 
	*/
	setEVProfile(evProfile) {
		this.m_object.setEVProfile(evProfile.getRawPointer());
	}
	getEVProfile() {
		return new gem.routesAndNavigation.EVProfile(this.m_object.getEVProfile());
	}
	setCarProfile(carprofile) {
		this.m_object.setCarProfile(carprofile.getRawPointer());
	}
	getCarProfile() {
		return new gem.routesAndNavigation.CarProfile(this.m_object.getCarProfile());
	}
}
/**Route class
 * @class gem.routesAndNavigation.Route
 * @memberof gem.routesAndNavigation
 */
gem.routesAndNavigation.Route = class route {
	constructor(routesrequest, routeId) {
		this.m_routesresult = routesrequest;
		this.routeId = routeId;
	}

	getRawPointer() {
		return this.m_routesresult.getRouteAt(this.routeId);
	}

	/**Get length in meters and estimated travel time in seconds for the route
	 * @returns {gem.core.TimeAndDistance}
	 */
	getTotalDistanceMAndTimeS() {
		return this.m_routesresult.getTotalDistanceMAndTimeS(this.routeId);
	}
	/**
	 * Get route sections size
	 * @returns {number} - number of route sections
	 */
	getNavigationInstructionSectionsSize() {
		return this.m_routesresult.getNavigationInstructionsSize(this.routeId);
	}
	/** Get list of traffic events affecting the route.
	 * @returns {gem.routesAndNavigation.RouteTrafficEventList} - list of traffic events along the route
	 */
	getTrafficEvents() {
		return new gem.routesAndNavigation.RouteTrafficEventList(this.m_routesresult.getTrafficEvents(this.routeId));
	}
	/**
	 * Get the list of navigation instruction for specified route section
	 * @param {number} nId - id for the route section
	 * @returns {gem.routesAndNavigation.RouteInstructionList} - routeInstructionList
	 */
	getNavigationInstructionSection(nId) {
		return new gem.routesAndNavigation.RouteInstructionList(this.m_routesresult.getNavigationInstruction(this.routeId, nId));
	}
	/**
	 *  Get list of route sections which are flat, that is, no significant elevation change.
	 * @returns {vector<gem.routesAndNavigation.SurfaceSection>}
	 */
	getTerrainSurfaceTypeSection() {
		return this.m_routesresult.getTerrainSurfaceTypeSection(this.routeId);
	}
	/**
	 * Get list of route sections which are of type road, typically, paved.
	 * @returns {vector<gem.routesAndNavigation.RoadTypeSection>}
	 */
	getTerrainRoadTypeSection() {
		return this.m_routesresult.getTerrainRoadTypeSection(this.routeId);
	}
	/**
	 * Get list of route sections which are abrupt, that is, they have a significant elevation change.
	 * @param {vector<float>} profileVector - categs The user list of steep categories. Each entry contains the max slope for the steep category as diffX / diffY. 
	 * @returns {vector<gem.routesAndNavigation.SteepSection>} - A positive value is for an ascension category, a negative value if a descent category
	 */
	getTerrainSteepeSections(profileVector) {
		return this.m_routesresult.getTerrainSteepeSections(this.routeId, profileVector);
	}
	/**
	 *  Get elevation samples list
	 * @param {number} nrOfSamples -  Number of samples
	 * @param {number} beginMeter - Begin distance on route for sample interval
	 * @param {number} endMeter -  End distance on route for sample interval
	 * @returns {vector<int>} - return Elevation samples
	 */
	getElevationSamples(nrOfSamples, beginMeter, endMeter) {
		return this.m_routesresult.getElevationSamples(this.routeId, nrOfSamples, beginMeter, endMeter);
	}
	getDominantRoads() {
		return this.m_routesresult.getDominantRoads(this.routeId);
	}
	getSegments() {
		return new gem.routesAndNavigation.RouteSegmentList(this.getRawPointer().getSegments());
	}
	toPTRoute() {
		return this.m_routesresult.toPTRoute(this.routeId);
	}
	getCoordinateOnRoute(distancemeters) {
		return this.m_routesresult.getCoordinateOnRoute(this.routeId, distancemeters);
	}
	getPath(start, end) {
		return this.m_routesresult.getPath(this.routeId, start, end);
	}
	exportAs(exportType) {
		return this.m_routesresult.exportAs(this.routeId, exportType);
	}
}
/**
 * @class gem.core.Landmark
 * @memberof gem.core
 */
gem.core.Landmark = class Landmark {
	constructor(lmkraw) {
		if (lmkraw === undefined) {
			this.m_landmarkPtr = new Module.Landmark(undefined);
			this.m_landmark = Module.Landmark.getRef(this.m_landmarkPtr);
		} else
			this.m_landmark = new Module.Landmark(lmkraw);
	}
	delete() {
		this.m_landmark.delete();
		if (this.m_landmarkPtr)
			this.m_landmarkPtr.delete();
	}
	isDeleted() {
		return this.m_landmark.isDeleted();
	}
	getRawPointer() {
		return this.m_landmark;
	}
	/**
	 *  Get the name of this landmark.
	 * @returns {string} - On return the parameter is set to the field value. It may be empty.
	 */
	getName() {
		return Module.Landmark.getName(this.m_landmark);
	}
	/**
	 * Gets the centroid coordinates.
	 * @returns {gem.core.Coordinates} On return the parameter is set to the actual value. The coordinates may be invalid. This aspect needs to be checked by the API user.
	 */
	getCoordinates() {
		return Module.Landmark.getCoordinates(this.m_landmark);
	}
	/** Returns the landmark ID.
	 * @returns {number} -  ID >= 0 Success.
	 */
	getLandmarkId() {
		return Module.Landmark.getLandmarkId(this.m_landmark);
	}
	/**
	 * get bounding box for the  geographic area.
	 * @returns {gem.core.BoundingBox}
	 */
	getBoundingBox() {
		return Module.Landmark.getBoundingBox(this.m_landmark);
	}
	/**
	 * Get formated text contact info
	 * @returns {string}
	 */
	getContactInfo() {
		return Module.Landmark.getContactInfo(this.m_landmark);
	}
	/**
	 * Get formated text extra info,each info separated by ','
	 * @returns {string}
	 */
	getExtraInfo() {
		return Module.Landmark.getExtraInfo(this.m_landmark);
	}
	/**
	 * Get the description of this landmark.
	 * @returns {string}
	 */
	getDescription() {
		return Module.Landmark.getDescription(this.m_landmark);
	}
	/**
	 * Get formated text of Address.
	 * @returns {string}
	 */
	getAddress() {
		return Module.Landmark.getAddress(this.m_landmark);
	}
	/**
	 * Get Address field content by name
	 * @param {gem.core.EAddressField} addressField  - field type
	 * @returns {string} 
	 */
	getAddressField(addressField) {
		return Module.Landmark.getAddressField(this.m_landmark, addressField);
	}
	/**
	 * Get page url or booking url or Wikipedia page
	 * @returns {string} url
	 */
	getUrl() {
		return Module.Landmark.getUrl(this.m_landmark);
	}
	/**
	 * 
	 * @param {gem.core.BitmapContainer} bitmapContainer -output for Landmark image
	 */
	getImage(bitmapContainer) {
		Module.Landmark.getImage(this.m_landmark, bitmapContainer.getRawPointer());
	}
	getLandmarkStoreId() {
		return Module.Landmark.getLandmarkStoreId(this.m_landmark);
	}
	getLandmarkStoreType() {
		return Module.Landmark.getLandmarkStoreType(this.m_landmark);
	}

	/**
	 * @callback wikipediainfocallback
	 * @param {object} wikipediacontainer - to use it please create a new gem.core.WikipediaContainer object around it
	 */
	/**
	 * Request wikipedia info. asnyc function
	 * @param {wikipediainfocallback} callbackfunction 
	 * @param {gem.core.ExternalInfo} externalInfo - object to handle external info
	 * @returns {gem.core.WikiDescriptionListener} - wikipedia listener object. Please use delete method on this object, after wikipedia
	 */
	requestWikipediaInfo(callbackfunction, externalInfo) {
		var constructorWiki = function (object, object2) {
			return new gem.core.WikipediaContainer(new Module.WikipediaContainer(object, object2));
		};
		return Module.Landmark.requestWikipediaInfo(this.m_landmark, callbackfunction, externalInfo.getRawPointer(), constructorWiki);
	}
	/**
	 * Get formatted name
	 * @returns {string}
	 */
	getFormatedName() {
		return Module.Landmark.getFormatedName(this.m_landmark);
	}
	/**
	 * Get formatted details
	 * @returns {string}
	 */
	getFormatedDetails() {
		return Module.Landmark.getFormatedDetails(this.m_landmark);
	}
	/**
	 * Get value for extra info key string
	 * @param {string} extrakey - key string
	 * @returns {string}
	 */
	getValueForExtraInfo(extrakey) {
		return Module.Landmark.getValueForExtraInfo(this.m_landmark, extrakey);
	}
	/**
	 * Returns if landmark has wikipedia info
	 * @param {gem.core.ExternalInfo} externalInfo - object to handle external info
	 * @returns {boolean}
	 */
	hasWikipediaInfo(externalInfo) {
		return Module.Landmark.hasWikipediaInfo(this.m_landmark, externalInfo.getRawPointer());
	}
	/**
	 * Get Country ISO code
	 * @returns {string}
	 */
	getCountryIsoCode() {
		return Module.Landmark.getCountryIsoCode(this.m_landmark);
	}
	/**
	 *  Get the country flag for the landmark.
	 * @param {gem.core.BitmapContainer} bitmapContainer - output image
	 * @returns {boolean} - true if the flag was found, false otherwise
	 */
	getCountryFlagImage(bitmapContainer) {
		return Module.Landmark.getCountryFlagImage(this.m_landmark, bitmapContainer.getRawPointer());
	}
	/**
	* Sets the image for the landmark using an icon ID.
 	* @param {number} usableIconId - The ID of the icon to use as the image.
 	*/
	setImage(usableIconId) {
		Module.Landmark.setImage(this.m_landmark, usableIconId);
	}

	setImagePointer(imagePointer)
	{
		Module.Landmark.setImagePointer(this.m_landmark,imagePointer);
	}
	getImagePointer()
	{
		return Module.Landmark.getImagePointer(this.m_landmark);
	}

	/**
	 * Sets the coordinates for the landmark.
	 * @param {gem.core.Coordinates} coordinates - The new coordinates for the landmark.
	 */
	setCoordinates(coordinates) {
		Module.Landmark.setCoordinates(this.m_landmark, coordinates);
	}

	/**
	 * Sets the name for the landmark.
	 * @param {string} name - The new name for the landmark.
	 */
	setName(name) {
		Module.Landmark.setName(this.m_landmark, name);
	}

	/**
	 * Sets the description for the landmark.
	 * @param {string} description - The new description for the landmark.
	 */
	setDescription(description) {
		Module.Landmark.setDescription(this.m_landmark, description);
	}

	/**
	 * Sets the image for the landmark using an image string.
	 * @param {string} imageString - The image string to use as the image.
	 */
	setImageByString(imageString) {
		Module.Landmark.setImagebyString(this.m_landmark, imageString);
	}

	/**
	 * Sets the waypoint track data for the landmark.
	 * @param {gem.d3Scene.Path} pathData - The new waypoint track data for the landmark.
	 */
	setWaypointTrackData(pathData) {
		Module.Landmark.setWaypointTrackData(this.m_landmark, pathData.getRawPointer());
	}
}
/**
 * @class gem.core.LandmarkList
 * @augments Em_Vector
 * @memberof gem.core
 */
gem.core.LandmarkList = class LandmarkList extends Em_Vector {
	constructor(rawPointer) {
		if (rawPointer === undefined)
			super(new Module.LandmarkList());
		else
			super(rawPointer);
	}
	/**
	 * Get item at position
	 * @param {number} idx -  position in vector 
	 * Returns item at position
	 * @returns {gem.core.Landmark}
	 */
	get(idx) {
		return new gem.core.Landmark(this.m_object.get(idx));
	}
	/**
	 * Insert a landmark
	 * @param {gem.core.Landmark} item 
	 */
	push_back(item) {
		this.m_object.insertLandmark(item.getRawPointer());
	}
	/**
	 * Delete landmark at a specifed id
	 * @param {number} number 
	 */
	delete_at(number) {
		this.m_object.deleteLandmarkAt(number);
	}
}
/**
 * @class gem.core.BitmapContainer
 * @augments Em_Object
 * @memberof gem
 */
gem.core.BitmapContainer = class bitmapContainer extends Em_Object {
	constructor(width, height) {
		super(new Module.OffBitmap(width, height));
	}
	/** Get width
	 * @returns {number}
	 */
	getWidth() {
		return this.m_object.getWidth();
	}
	/**
	 * Get Height
	 * @returns {number}
	 */
	getHeight() {
		return this.m_object.getHeight();
	}
	/**
	 * convert to byte array
	 * @returns {Array.<Byte>}
	 */
	toImageData() {
		return this.m_object.toImageData();
	}
	toBMP()
	{
		let rawBuffer = this.m_object.toImageData();
		let width = this.getWidth();
		let height = this.getHeight();
		const headerSize = 14;         
    const dibHeaderSize = 40;
    const pixelArrayOffset = headerSize + dibHeaderSize; 
    const bitsPerPixel = 32;       // 32 bits per pixel for RGBA
    const rowSize = Math.ceil((bitsPerPixel * width) / 32) * 4; 
    const pixelDataSize = rowSize * height;
    const fileSize = pixelArrayOffset + pixelDataSize;

    // Create the BMP file and DIB header
    const header = new ArrayBuffer(pixelArrayOffset);
    const headerView = new DataView(header);

    // BMP Header (14 bytes)
    headerView.setUint16(0, 0x424D, false);            // Signature 'BM'
    headerView.setUint32(2, fileSize, true);           // File size
    headerView.setUint32(6, 0, true);                  // Reserved
    headerView.setUint32(10, pixelArrayOffset, true);  // Offset to pixel data

    // DIB Header (40 bytes)
    headerView.setUint32(14, dibHeaderSize, true);     // DIB header size
    headerView.setInt32(18, width, true);              // Image width
    headerView.setInt32(22, -height, true);            // Image height (top-down)
    headerView.setUint16(26, 1, true);                 // Color planes (must be 1)
    headerView.setUint16(28, bitsPerPixel, true);      // Bits per pixel (32 for RGBA)
    headerView.setUint32(30, 0, true);                 // Compression (no compression)
    headerView.setUint32(34, pixelDataSize, true);     // Size of raw bitmap data
    headerView.setInt32(38, 2835, true);               // Horizontal resolution
    headerView.setInt32(42, 2835, true);               // Vertical resolution
    headerView.setUint32(46, 0, true);                 // Colors in palette
    headerView.setUint32(50, 0, true);                 // Important colors (0 = all)

    // Combine headers and RGBA pixel data into the final buffer
    const bmpBuffer = new Uint8Array(fileSize);
    bmpBuffer.set(new Uint8Array(header), 0);           // Add BMP and DIB headers
    bmpBuffer.set(new Uint8Array(rawBuffer), pixelArrayOffset); // Add RGBA pixel data
	return bmpBuffer;
	}
}
/**
 * @class gem.content.ContentStoreItem
 * @augments Em_Object
 * @memberof gem.content
 */
gem.content.ContentStoreItem = class ContentStoreItem extends Em_Object {
	/**
	 * Delete content from local
	 */
	deleteContent() {
		Module.ContentStoreItem.deleteContent(this.m_object);
	}
	/**
	 * Check if content is updatable
	 * @returns {boolean}
	 */
	isUpdatable() {
		return Module.ContentStoreItem.isUpdatable(this.m_object);
	}
	/**
	 * Get Download Progress
	 * @returns {number} - progress in percentage
	 */
	getDownloadProgress() {
		return Module.ContentStoreItem.getDownloadProgress(this.m_object);
	}
	/**
	 * Check if the content can be deleted
	 * @returns {boolean}
	 */
	canDeleteContent() {
		return Module.ContentStoreItem.canDeleteContent(this.m_object);
	}
	/**
	 * Check if is downloading
	 * @returns {boolean}
	 */
	isDownloading() {
		return Module.ContentStoreItem.isDownloading(this.m_object);
	}
	/**
	 * Download content item
	 * @param {gem.content.StoreProgressListener} storeProgressListener 
	 */
	download(storeProgressListener) {
		Module.ContentStoreItem.download(this.m_object, storeProgressListener.getRawPointer());
	}
	/**
	 * Get name of the content store item
	 * @returns {string}
	 */
	getName() {
		return Module.ContentStoreItem.getName(this.m_object);
	}
	/**
	 * Check if item is downloaded
	 * @returns {boolean}
	 */
	isCompleted() {
		return Module.ContentStoreItem.isCompleted(this.m_object);
	}
	/**
	 * Check if image preview is available
	 * @returns {boolean}
	 */
	isImagePreviewAvailable() {
		return Module.ContentStoreItem.isImagePreviewAvailable(this.m_object);
	}
	/**
	 * Get Image Preview
	 * @param {gem.core.BitmapContainer} bitmap - Output bitmap
	 * @returns {boolean}
	 */
	getImagePreview(bitmap) {
		return Module.ContentStoreItem.getImagePreview(this.m_object, bitmap.getRawPointer());
	}
	/**
	 * Get Country Codes list
	 *  @returns {vector<string>}
	 */
	getCountryCodes() {
		return Module.ContentStoreItem.getCountryCodes(this.m_object);
	}
}
/**
 * @class gem.content.ContentStoreList
 * @augments Em_Object
 * @memberof gem.content
 */
gem.content.ContentStoreList = class ContentStoreList extends Em_Vector {
	/**
	 * Get store item at position in list
	 * @param {number} position
	 * @returns {gem.content.ContentStoreItem} - store item
	 */
	get(position) {
		return new gem.content.ContentStoreItem(this.m_object.get(position));
	}
}
/**marker class
 * @class gem.d3Scene.OverlayItem
 * @augments Em_Object
 * @memberof gem.d3Scene
 */
gem.d3Scene.OverlayItem = class OverlayItem extends Em_Object {
	/**
	 * Get Peview url
	 * @returns {string}
	 */
	getPreviewUrl() {
		return Module.OverlayItem.getPreviewUrl(this.m_object);
	}
	/**
	 * Get Preview data 
	 * @returns {string} - formatted as json
	 */
	getPreviewData() {
		if (!this.previewData)
			this.previewData = Module.OverlayItem.getPreviewData(this.m_object);
		return this.previewData;
	}
	/**
	 * Get Coordinates
	 * @returns {gem.core.Coordinates}
	 */
	getCoordinates() {
		if (!this.coordinates)
			this.coordinates = Module.OverlayItem.getCoordinates(this.m_object);
		return this.coordinates;
	}
	/**
 	* Retrieves the preview data for the object.
 	* @returns {string} The preview data.
 	*/
	getInfo() {
		return this.getPreviewData();
	}
	/**
 	* Retrieves the image for the overlay item.
 	* @param {gem.core.BitmapContainer} bitmapContainer - The container for the bitmap image.
 	*/
	getImage(bitmapContainer) {
		Module.OverlayItem.getImage(this.m_object, bitmapContainer.getRawPointer());
	}
	getOverlayInfoImage(bitmapContainer)
	{
		Module.OverlayItem.MarkergetOverlayInfoImage(this.m_object, bitmapContainer.getRawPointer());
	}
	getImagePointer()
	{
		return Module.OverlayItem.getImagePointer(this.m_object);
	}
	/**
 	* Caches the preview data and coordinates for the object.
 	*/
	cacheData() {
		this.getPreviewData();
		this.getCoordinates();
	}
}
gem.d3Scene.OverlayItemList = class OverlayItemList extends Em_Vector {
	/**
	 * Get store item at position in list
	 * @param {number} position
	 * @returns {gem.d3Scene.OverlayItem} - overlay item
	 */
	get(position) {
		return new gem.d3Scene.OverlayItem(this.m_object.get(position));
	}
}
/**NavigationListener class
 * @class gem.routesAndNavigation.NavigationListener
 * @augments Em_Object
 * @memberof gem.routesAndNavigation
 */
gem.routesAndNavigation.NavigationListener = class navigationListener extends Em_Object {
	constructor() {
		super(new Module.NavigationListener());
	}
	registerNavInstructionUpdateListener(callback) {
		this.m_object.registerNavInstructionUpdateListener(callback);
	}
	registerNavInstructionOnSoundListener(callback) {
		this.m_object.registerNavInstructionOnSoundListener(callback);
	}
}
/**routeInstructionList class
 * @class gem.routesAndNavigation.RouteInstructionList
 * @augments Em_Vector
 * @memberof gem.routesAndNavigation
 */
gem.routesAndNavigation.RouteInstructionList = class routeInstructionList extends Em_Vector {
	constructor(object) {
		if (object === undefined) {
			super(new Module.vector$RouteInstruction$())
		} else {
			super(object);
		}
	}
	get(number) {
		return new gem.routesAndNavigation.RouteInstruction(this.m_object.get(number));
	}
	push_back(object) {
		this.m_object.push_back(object.getRawPointer());
	}
}
/**routeInstruction class
 * @class gem.routesAndNavigation.RouteInstruction
 * @augments Em_Object
 * @memberof gem.routesAndNavigation
 */
gem.routesAndNavigation.RouteInstruction = class routeInstruction extends Em_Object {
	/** Get turn instruction as text
	 *@returns {string}
	 */
	getTurnInstructionString() {
		return Module.RouteInstruction.getTurnInstructionString(this.m_object);
	}
	/**
	 * Get the turn image in a bitmap
	 * @param {gem.core.BitmapContainer} bitmapContainer - output bitmap
	 */
	getTurnImageInBitmap(bitmapContainer) {
		Module.RouteInstruction.getTurnImageInBitmap(this.m_object, bitmapContainer.getRawPointer());
	}
	/**
	 * Get textual description for the follow road information.
	 * @returns {string}
	 */
	getFollowRoadInstruction() {
		return Module.RouteInstruction.getFollowRoadInstruction(this.m_object);
	}

	/**
	 * Get Coordinates
	 * @returns {gem.core.Coordinates}
	 */
	getCoordinates() {
		return Module.RouteInstruction.getCoordinates(this.m_object);
	}
	/**
	 * Get Country Code ISO
	 * @returns {string}
	 */
	getCountryCodeISO() {
		return Module.RouteInstruction.getCountryCodeISO(this.m_object);
	}
	/** Get remaining travel distance in meters and remaining traveling time in seconds.
	 * @returns {gem.core.TimeAndDistance}
	 */
	getRemainingTravelTimeDistance() {
		return Module.RouteInstruction.getRemainingTravelTimeDistance(this.m_object);
	}
	/**
	 * Gets distance to the next turn in meters, time in seconds.
	 * @returns {gem.core.TimeAndDistance}
	 */
	getTimeDistanceToNextTurn() {
		return Module.RouteInstruction.getTimeDistanceToNextTurn(this.m_object);
	}
	/**
	 * Get the traveled distance in meters and the traveled time in seconds.
	 * @returns {gem.core.TimeAndDistance}
	 */
	getTraveledTimeDistance() {
		return Module.RouteInstruction.getTraveledTimeDistance(this.m_object);
	}
	/**
	 * Get image for the realistic turn information.
	 * @param {gem.core.BitmapContainer} bitmapContainer - output bitmap. This object will be modified by this method.
	 */
	getRealisticNextTurnImage(bitmapContainer) {
		Module.RouteInstruction.getRealisticNextTurnImage(this.m_object, bitmapContainer.getRawPointer());
	}
	/**
	 * Check if follow road information is available.
	 * @returns {boolean}
	 */
	hasFollowRoadInfo() {
		return Module.RouteInstruction.hasFollowRoadInfo(this.m_object);
	}
	/**
	 * Checks if turn information is available.
	 * @returns {boolean}
	 */
	hasTurnInfo() {
		return Module.RouteInstruction.hasTurnInfo(this.m_object);
	}
	/**
	 * Check if signpost information is available.
	 * @returns {boolean}
	 */
	hasSignpostInfo() {
		return Module.RouteInstruction.hasSignpostInfo(this.m_object);
	}
	/**
	 * 
	 * @param {*} bitmapContainer - output bitmap
	 * @param {*} backgroundColor - background color
	 * @param {*} foregroundColor - foreground color
	 */
	getAbstractGeometryImage(bitmapContainer, backgroundColor, foregroundColor) {
		Module.RouteInstruction.getAbstractGeometryImage(this.m_object, bitmapContainer.getRawPointer(), backgroundColor, foregroundColor);
	}
	/**
 	* Converts the current route instruction to a Public Transit route instruction.
 	* @returns {object} The converted Public Transit route instruction.
 	*/
	toPTRouteInstruction() {
		return Module.RouteInstruction.toPTRouteInstruction(this.m_object);
	}
	/**
 	* Converts the current route instruction to an Electric Vehicle route instruction.
 	* @returns {gem.routesAndNavigation.EVRouteInstruction} The converted Electric Vehicle route instruction.
 	*/
	toEVRouteInstruction() {
		return new gem.routesAndNavigation.EVRouteInstruction(Module.RouteInstruction.toEVRouteInstruction(this.m_object));
	}
}
/**StoreProgressListener class
 * @class gem.content.StoreProgressListener
 * @augments Em_Object
 * @memberof gem.content
 */
gem.content.StoreProgressListener = class StoreProgressListener extends Em_Object {
	constructor() {
		super(new Module.StoreProgressListener());
	}

	/**
	 * 
	 * @param {*} callback 
	 */
	registerCompleteCallback(callback) {
		this.m_object.registerCompleteCallback(callback);
	}
	/**
	 * 
	 * @param {*} callback 
	 */
	registerProgressCallback(callback) {
		this.m_object.registerProgressCallback(callback);
	}
}
/**
 * @class gem.core.App
 *@hideconstructor
 *@memberof gem.core
 */
gem.core.App = class App {
	/**
	 * 
	 * @param {*} callbackfunction 
	 */
	static registerPostInitializeCallback(callbackfunction) {
		Module.RegisterPostInitializeCallback(callbackfunction);
	}
	/**
	 * Destroy app resources. Should be called at page closing
	 */
	static destroyApp() {
		try {
			Module.destroyApp();
		} catch (e) {
			Module.destroyHasBeenCalled = true;
		}
	}
	/**
	 * 
	 * @param {*} callbackfunction 
	 */
	static registerPreRenderCallback(callbackfunction) {
		Module.registerPreRenderCallback(callbackfunction);
	}
	/**
	 * Update the url header with informations about location ( lat, lon, z = zoom level )
	 */
	static updateHeader() {
		if (history.pushState) {
			let defaultView = gem.core.App.getDefaultScreen().getDefaultMapView();
			let currentPosition = defaultView.getCursorPositionWGS();
			let zoomLevel = defaultView.getZoomLevel();
			var newurl = window.location.protocol + "//" + window.location.host + window.location.pathname + "?lat=" + currentPosition.latitude + "&long=" + currentPosition.longitude + "&z=" + zoomLevel;
			window.history.pushState({
				path: newurl
			}, '', newurl);
		}
	}
	/**
	 * Register initial call function. Must be added before initApp() call
	 * @param {callback} callFunction 
	 */
	static registerInitialCallFunction(callFunction) {
		gem.core.App.m_initialFunction = callFunction;
	}
	/**
	 * Register route calculation from url parameters. Must be added before initApp() call
	 * @param {callback} callFunction 
	 */
	static registerRouteCalculationFromParameters(callFunction) {
		gem.core.App.m_routeCalculationFromParam = callFunction;
	}
	static addScriptToDom(scriptCode) {
		return new Promise(function (resolve, reject) {
			var script = document.createElement('script');
			script.nonce = 'matching-nonce-value';
			var blob = new Blob([scriptCode], {
				type: 'application/javascript'
			});
			var objectUrl = URL.createObjectURL(blob);
			script.src = objectUrl;
			script.onload = function () {
				script.onload = script.onerror = null; // Remove these onload and onerror handlers, because these capture the inputs to the Promise and the input function, which would leak a lot of memory!
				URL.revokeObjectURL(objectUrl); // Free up the blob. Note that for debugging purposes, this can be useful to comment out to be able to read the sources in debugger.
				resolve();
			}
			script.onerror = function (e) {
				script.onload = script.onerror = null; // Remove these onload and onerror handlers, because these capture the inputs to the Promise and the input function, which would leak a lot of memory!
				URL.revokeObjectURL(objectUrl);
				console.error('script failed to add to dom: ' + e);
				reject(e.message || "(out of memory?)");
			}
			document.body.appendChild(script);
		});
	}
	static syncStorageLandmarks(direction) {
		if (direction) {
			try {
				//create your directory where we keep our persistent data
				FS.mkdir('/Data/Landmarks');

			} catch (error) {

			}
			try {
				FS.mount(IDBFS, {}, '/Data/Landmarks');
				FS.syncfs(direction, function (err) {
					assert(!err);
					setTimeout(() => gem.core.App.syncContentWithStorage(true), 0);
				});
			} catch (error) {
				gem.core.App.usePersistentStorage = 0;
			}
		} else {
			FS.syncfs(function (err) { });
		}
	}
	static syncContentWithStorage(direction) {
		if (direction) {
			try {
				//create your directory where we keep our persistent data
				FS.mkdir('/Data/Maps');

			} catch (error) {

			}
			FS.mount(IDBFS, {}, '/Data/Maps');
			var syncFlag = 0;
			FS.syncfs(direction, function (err) {
				assert(!err);
				if (Module.refreshContentStore !== undefined) {
					Module.refreshContentStore();
					if (gem.core.App.persistentStorageLoadedCb)
						gem.core.App.persistentStorageLoadedCb();
				}
				syncFlag = 1;
			});
		} else {
			FS.syncfs(function (err) { });
		}

	}
	static preRun(worldMapFileI) {

		let baseURI = baseURL + "sdk/js/";
		let worldMapFile = baseURI + worldMapFileI;
		const searchString = window.location.search;
		const urlParams = new URLSearchParams(searchString);
		if (urlParams.has("app_key")) {
			/**
				*@public
				@memberof gem.core
				*@property {string} gem.core.App.token - App token. Must be set before initApp() call
				*/
			gem.core.App.token = urlParams.get("app_key");
		}
		Module.appAuthorizationKey = gem.core.App.token !== undefined ? gem.core.App.token : "";
		var n = worldMapFile.lastIndexOf("/");
		var filename = worldMapFile.substring(n);
		Module.noImageDecoding = true;
		FS.createPath('/', 'Data', true, true);
		FS.createPath('/Data', 'Res', true, true);
		FS.createPath('/Data/Res', 'Obj', true, true);
		FS.createPath('/Data/Res', 'Renderer', true, true);
		FS.createPath('/Data/Res', 'Weather', true, true);
		FS.createPath('/Data', 'SceneRes', true, true);
		gem.core.App.pathDataCreated = true;
		gem.core.App.usePersistentStorage = gem.core.App.usePersistentStorage && (window.indexedDB || indexedDB) ? 1 : 0;
		if (navigator.userAgent.indexOf("Firefox") != -1) {
			let db = indexedDB.open("test");
			db.onerror = function () {
				gem.core.App.usePersistentStorage = 0;
			};
			db.onsuccess = function () {
				gem.core.App.syncStorageLandmarks(true);
			};
		} else {
			if (gem.core.App.usePersistentStorage) {
				gem.core.App.syncStorageLandmarks(true);
			}
		}
		try {
			gem.core.App._processFileQueue();
			FS.createPreloadedFile("/Data/Res", filename, baseURI + "res/map/latest/", true, true);
			//FS.createPath('/Data/SceneRes', 'SceneConfig', true, true);
			//FS.createPath('/Data/SceneRes', 'Schemas', true, true);

			if (gem.core.App.defaultStyleFile !== undefined) {
				FS.createPreloadedFile("/Data/SceneRes", 'Basic_1-1_1_1.style', gem.core.App.defaultStyleFile, true, true);
			} else {
				FS.createPreloadedFile("/Data/SceneRes", 'Basic_1-1_1_1.style', baseURI + 'Basic_1-1_1_1.style', true, true);
			}

			if (gem.core.App.preloadeddata !== undefined) {
				if (gem.core.App.preloadeddata === "satellite") {
					FS.createPath('/Data', 'Temporary', true, true);
					FS.createPath('/Data/Temporary', 'Tiles', true, true);
					FS.createPreloadedFile("/Data/Temporary/Tiles", 'prv3_prm1.vdb', baseURI + 'AppData/Data/Temporary/Tiles/prv3_prm1.vdb', true, true);
				}
			}

			FS.createPreloadedFile("/Data/Res", 'Countries.proto', baseURI + 'res/lang/generic/Countries.proto', true, true);
			FS.createPreloadedFile("/Data/Res", 'Icon.db', baseURI + 'res/icon/SDL/Icon.db', true, true);
			FS.createPreloadedFile("/Data/Res", 'Ipa_Lhp.pst', baseURI + 'AppData/Data/Res/Ipa_Lhp.pst', true, true);
			FS.createPreloadedFile("/Data/Res", 'Ipa_XSampa.pst', baseURI + 'AppData/Data/Res/Ipa_XSampa.pst', true, true);
			//FS.createPreloadedFile("/Data/Res", 'links.stx', baseURI + 'AppData/Data/Res/links.stx', true, true);
			//FS.createPreloadedFile("/Data/Res",'MapResource.proto',baseURI+'/AppData/Data/Res/MapResource.proto', true, true);
			FS.createPreloadedFile("/Data/Res", 'MapScheme.proto', baseURI + 'res/lang/generic/MapScheme.proto', true, true);
			//FS.createPreloadedFile("/Data/Res", 'nocap.stx', baseURI + 'AppData/Data/Res/nocap.stx', true, true);
			//FS.createPreloadedFile("/Data/Res", 'squarecap.stx', baseURI + 'AppData/Data/Res/squarecap.stx', true, true);
			FS.createPreloadedFile("/Data/Res", 'Traffic.proto', baseURI + 'res/lang/generic/Traffic.proto', true, true);

			FS.createPreloadedFile("/Data/Res", 'Translations.proto', baseURI + 'res/lang/Controller/Translations.proto', true, true);

			FS.createPreloadedFile("/Data/Res", 'Weather.proto', baseURI + 'res/lang/generic/Weather.proto', true, true);
			FS.createPreloadedFile("/Data/Res/Obj", 'arrow.obj', baseURI + 'AppData/Data/Res/Obj/arrow.obj', true, true);

			FS.createPreloadedFile("/Data/Res/Renderer", 'Font-2.sys', baseURI + 'AppData/Data/Res/Renderer/Font-2.sys', true, true);
			FS.createPreloadedFile("/Data/Res/Renderer", 'Font-Arabic.sys', baseURI + 'AppData/Data/Res/Renderer/Font-Arabic.sys', true, true);
			FS.createPreloadedFile("/Data/Res/Renderer", 'Font-Bold2.sys', baseURI + 'AppData/Data/Res/Renderer/Font-Bold2.sys', true, true);
			FS.createPreloadedFile("/Data/Res/Renderer", 'Font-Cambodian.sys', baseURI + '/AppData/Data/Res/Renderer/Font-Cambodian.sys', true, true);
			FS.createPreloadedFile("/Data/Res/Renderer", 'Font-Italic2.sys', baseURI + 'AppData/Data/Res/Renderer/Font-Italic2.sys', true, true);
			FS.createPreloadedFile("/Data/Res/Renderer", 'Font-Myanmar.sys', baseURI + 'AppData/Data/Res/Renderer/Font-Myanmar.sys', true, true);
			FS.createPreloadedFile("/Data/Res/Renderer", 'Fonts2.chf', baseURI + 'AppData/Data/Res/Renderer/Fonts2.chf', true, true);
			FS.createPreloadedFile("/Data/Res/Renderer", 'Font-Ethiopia.sys', baseURI + 'AppData/Data/Res/Renderer/Font-Ethiopia.sys', true, true);
			//FS.createPreloadedFile("/Data/SceneRes/SceneConfig",'SceneResource.proto',baseURI+'/AppData/Data/SceneRes/SceneConfig/SceneResource.proto', true, true);				
			//FS.createPreloadedFile("/Data/SceneRes/SceneConfig",'SceneResource.json',baseURI+'/AppData/Data/SceneRes/SceneConfig/SceneResource.json', true, true);
			//FS.createPreloadedFile("/Data/SceneRes/Schemas",'SceneResource_Schema.json',baseURI+'/AppData/Data/SceneRes/Schemas/SceneResource_Schema.json', true, true);				

			FS.createPreloadedFile("/Data/Res/Weather", 'atmos.bmp', baseURI + 'AppData/Data/Res/Weather/atmos.bmp', true, true);
			FS.createPreloadedFile("/Data/Res/Weather", 'colorpallet.tga', baseURI + 'AppData/Data/Res/Weather/colorpallet.tga', true, true);
			FS.createPreloadedFile("/Data/Res/Weather", 'radar_colorpalette.tga', baseURI + 'AppData/Data/Res/Weather/radar_colorpalette.tga', true, true);
			FS.createPreloadedFile("/Data/Res/Weather", 'SunFlare.dat', baseURI + 'AppData/Data/Res/Weather/SunFlare.dat', true, true);
			FS.createPreloadedFile("/Data/Res/Weather", 'greyscale_colorbar.bmp', baseURI + 'AppData/Data/Res/Weather/greyscale_colorbar.bmp', true, true);
			FS.createPreloadedFile("/Data/Res", 'Social.proto', baseURI + 'AppData/Data/Res/Social.proto', true, true);
		
		} catch (error) {

		}
	}
	/**
	 * Set the path to the style to be used at startup.Must be added before initApp() call
	 * @param {string} styleFilePath 
	 */
	static setDefaultStyle(styleFilePath) {
		gem.core.App.defaultStyleFile = styleFilePath;
	}
	static setPreloadedData(preloadeddata) {
		gem.core.App.preloadeddata = preloadeddata;
	}
	/**
	 * Start the initilization process.Is an async process.Getting the resources. 
	 * Please use gem.core.App.registerInitialCallFunction before calling this method,if you want to apply settings at application startup.
	 */
	static createElementCopyright() {
		let cvs = document.getElementById("canvas");
		let copyrightDiv = document.createElement("div");
		copyrightDiv.className = 'gem-no-select';
		copyrightDiv.setAttribute("id", "copyrightDiv");
		copyrightDiv.style.backgroundColor = "rgba(255, 255, 255, 0.8)";
		copyrightDiv.style.bottom = "0";
		copyrightDiv.style.right = "4px";
		copyrightDiv.style.position = "absolute";
		copyrightDiv.style.fontSize = "small";
		copyrightDiv.style.height = "auto";
		copyrightDiv.style.padding = "0px";
		copyrightDiv.innerHTML += "&copy;&bull;<a href='https://www.magiclane.com'>MagicLane</a>&bull;<a href='https://www.openstreetmap.org/copyright'>OpenStreetMap</a>";
		copyrightDiv.style.zIndex = "999999";
		let button = document.createElement("Button");
		button.setAttribute("id", "buttonCopyright");
		button.setAttribute("type", "button");
		button.style.position = "absolute";
		button.style.bottom = "0";
		button.style.right = "2px";
		button.textContent = 'i';
		button.style.zIndex = "999999";
		button.onmouseover = function () {
			let button = document.getElementById("buttonCopyright");
			let cDiv = document.getElementById("copyrightDiv");
			cDiv.style.right = button.offsetWidth + 2 + "px";
			cDiv.style.visibility = "visible";
		};
		button.onmouseleave = function () {
			let cDiv = document.getElementById("copyrightDiv");
			cDiv.style.visibility = "hidden";
		};
		button.onmousedown = function () {
			let cDiv = document.getElementById("copyrightDiv");
			if (cDiv.style.visibility.localeCompare("hidden") === 0) {
				cDiv.style.visibility = "visible";
			} else
				cDiv.style.visibility = "hidden";
		};
		cvs.parentElement.appendChild(copyrightDiv);
		cvs.parentElement.appendChild(button);

		function myFunction(x) {
			if (x.matches) { // If media query matches
				let button = document.getElementById("buttonCopyright");
				button.style.visibility = "visible";
				let cDiv = document.getElementById("copyrightDiv");
				cDiv.style.visibility = "hidden";

			} else {
				let button = document.getElementById("buttonCopyright");
				button.style.visibility = "hidden";
				let cDiv = document.getElementById("copyrightDiv");
				cDiv.style.visibility = "visible";
				cDiv.style.right = "2px";
			}
		}

		var x = window.matchMedia("(max-width: 720px)");
		myFunction(x); // Call listener function at run time
		x.addEventListener('change', myFunction); // Attach listener function on state changes  
	}

	static createLoadingCircle(parentDiv) {
		let svgns = "http://www.w3.org/2000/svg";
		let wrapperDiv = document.createElement("div");
		wrapperDiv.setAttribute("id", "wrapper");
		parentDiv.insertBefore(wrapperDiv, parentDiv.firstChild);
		let profilemainloader = document.createElement("div");
		profilemainloader.classList.add("profile-main-loader");
		wrapperDiv.appendChild(profilemainloader);
		let loader = document.createElement("div");
		loader.classList.add("loader");
		profilemainloader.appendChild(loader);
		let svg = document.createElementNS(svgns, "svg");
		svg.classList.add("circular-loader");
		svg.setAttribute("viewBox", "25 25 50 50");
		loader.appendChild(svg);
		let circle = document.createElementNS(svgns, 'circle');
		circle.classList.add("loader-path");
		circle.setAttributeNS(null, 'cx', 50);
		circle.setAttributeNS(null, 'cy', 50);
		circle.setAttributeNS(null, 'r', 20);
		circle.setAttributeNS(null, 'style', 'fill: none; stroke: #70c542; stroke-width: 2px;');
		svg.appendChild(circle);
	}
	static parseOptions() {
		let defaultView = gem.core.App.getDefaultScreen().getDefaultMapView();

		if (gem.core.App.initOptions.center !== undefined) {
			let lat = gem.core.App.initOptions.center[0];
			let lon = gem.core.App.initOptions.center[1];
			let alt = gem.core.App.initOptions.center[2];
			if (alt === undefined)
				alt = 1000;
			let coordinates = {
				latitude: lat,
				longitude: lon,
				altitude: alt,
				bearing: 0
			};
			let msecFlightDuration = 0;
			defaultView.centerOnCoordinates(coordinates, msecFlightDuration);
		}
		if (gem.core.App.initOptions.zoom !== undefined) {
			defaultView.setZoomLevel(gem.core.App.initOptions.zoom);
		}
		gem.core.App.defaultAppScreen.initFinished();
	}
	/**Init App . Mandatory to be called.
	 *@param {object} initOptions - init options
	 *@returns {gem.core.AppScreen} - returns AppScreen Object
	 * 
	 */
	static initAppScreen(initOptions) {
		return gem.core.App.initApp(initOptions);
	}
	static inIframe() {
		try {
			return window.self !== window.top;
		} catch (e) {
			return true;
		}
	}

	static _createFile(path, filename, url, canRead, canWrite) {
		
	  
		if (gem.core.App.pathDataCreated) {
		  // Use FS.createPreloadedFile before WASM is loaded
		  FS.createPreloadedFile(
			path,       // Path in the filesystem
			filename,   // File name
			url,        // URL of the file
			canRead,    // Read permission
			canWrite    // Write permission
		  );
		} else {
		  // Fetch file manually
		  fetch(url)
			.then(response => response.arrayBuffer())
			.then(data => {
			  if ( gem.core.App.pathDataCreated && typeof FS !== "undefined") {
				// If FS is available, create the file
				FS.createDataFile(
				  path,            // Path in the filesystem
				  filename,        // File name
				  new Uint8Array(data), // File data as Uint8Array
				  canRead,         // Read permission
				  canWrite         // Write permission
				);
				console.log(`${filename} has been loaded into ${path}`);
			  } else {
				// If FS is not available, store file info in the queue
				gem.core.App.fileQueue.push({ path, filename, data: new Uint8Array(data), canRead, canWrite });
				console.warn(`FS not available. Queued ${filename} for later.`);
			  }
			})
			.catch(err => console.error(`Failed to load ${url}: ${err.message}`));
		}
	  }

	  static _processFileQueue() {
		if (typeof FS === "undefined") {
		  console.warn("FS is still not available. Cannot process queued files yet.");
		  return;
		}
	  
		gem.core.App.fileQueue.forEach(file => {
		  FS.createDataFile(
			file.path,
			file.filename,
			file.data,
			file.canRead,
			file.canWrite
		  );
		  console.log(`${file.filename} has been processed and loaded into ${file.path}`);
		});
	  
		gem.core.App.fileQueue.length = 0; // Clear the queue after processing
	  }

	/**Init App . Mandatory to be called.
	 * @param {object} initOptions - init options
	 * @param {string} initOptions.container - container element id
	 * @param {string} [initOptions.style] - url to map style
	 * @param {object} [initOptions.center] - map start-up center coordinate of type [latitude, longitude, altitude]
	 * @param {number} [initOptions.zoom] - map start-up zoom level 
	 */
	static initApp(initOptions) {
		gem.core.App.fileQueue = [];
		// fix for mobile
		window.addEventListener('load', () => {
			const viewport = document.querySelector('meta[name=viewport]');
			if (viewport)
				viewport.setAttribute('content', viewport.content + ', height=' + window.innerHeight + ', width=' + window.innerWidth);
		});

		if (initOptions !== undefined) {
			let containerDiv = document.getElementById(initOptions.container);
			gem.core.App.containerDiv = containerDiv;
			containerDiv.style.overflow = "hidden";
			let canvasDiv = document.createElement("canvas");
			canvasDiv.classList.add("emscripten");
			canvasDiv.setAttribute("id", "canvas");
			canvasDiv.addEventListener('contextmenu', e => {
				e.preventDefault();
			});
			canvasDiv.tabIndex = "0";
			containerDiv.appendChild(canvasDiv);
			gem.core.App.createLoadingCircle(containerDiv);
			if (initOptions.style !== undefined) {
				gem.core.App.defaultStyleFile = initOptions.style;
			}
			gem.core.App.enableDebug = initOptions.debugMode;
			gem.core.App.multiThread = initOptions.multiThread;
			gem.core.App.onlineStatus = initOptions.isOnlineAllowed;
			gem.core.App.usePersistentStorage = initOptions.usePersistentStorage && (window.indexedDB || indexedDB) ? 1 : 0;
			gem.core.App.persistentStorageLoadedCb = initOptions.persistentStorageLoadedCb;
			//Module.useperisistentstorage = initOptions.usePersistentStorage ? 1 : 0;
		}
		if (gem.core.App.inIframe()) {
			document.body.onmouseleave = function (ev) {
				let cvs = document.getElementById("canvas");
				if (cvs) {
					var event = new MouseEvent('mouseup', {
						'view': window,
						'bubbles': true,
						'cancelable': true
					});
					cvs.dispatchEvent(event);
				}
			};
		}
		Module = {
			preRun: [],
			postRun: [],
			print: (function () {
				return function (text) { };
			})(),
			printErr: function (text) {
				text = Array.prototype.slice.call(arguments).join(' ');
				console.error(text);
			},
			canvas: (function () {
				var canvas = document.getElementById('canvas');
				return canvas;
			})(),
		};
		if (gem.core.App.enableLoggingFlag)
			Module.print = (function () {
				return function (text) {
					text = Array.prototype.slice.call(arguments).join(' ');
					console.log(text);
				};
			})();
		gem.core.App.initOptions = initOptions;
		try {
			Module['preRun'].push(function () {
				gem.core.App.preRun('WM_7_403.map');
				Module.useperisistentstorage = 0;
				Module.gemAppVariant = gem.core.App.appVariant;
				if (gem.core.App.onlineStatus !== undefined) {
					Module.onlineStatus = gem.core.App.onlineStatus;
				}
			});
			Module['postRun'].push(function () {
				gem.core.App.initializeStructures();
				var canvas = document.getElementById('canvas');
				var wrapper = document.getElementById('wrapper');
				if (wrapper !== null && wrapper !== undefined)
					wrapper.remove();
				canvas.style.visibility = 'visible';
				if (!Module.useperisistentstorage) {
					if (parseInt(canvas.style.width) < 1 || parseInt(canvas.style.height) < 1) {
						console.warn("Please use a valid canvas width & height");
						gem.core.App.registerPostInitializeCallback(function () {
							if (gem.core.App.initOptions)
								gem.core.App.parseOptions();
							gem.core.App.parseParameters(gem.core.App.m_routeCalculationFromParam);
							var isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
							if (isMobile) {
								gem.core.App.getDefaultScreen().getDefaultMapView().setMapViewDetailsQualityLevel(0);
							}
							if (gem.core.App.m_initialFunction)
								gem.core.App.m_initialFunction();
						});
					} else {
						if (gem.core.App.initOptions)
							gem.core.App.parseOptions();

						if (gem.core.App.m_initialFunction)
							gem.core.App.m_initialFunction();
						gem.core.App.parseParameters(gem.core.App.m_routeCalculationFromParam);
					}
					window.addEventListener("beforeunload", e => {
						gem.core.App.destroyApp();
					}, {
						once: true
					});
				}
				//Module.enableLocation();
				gem.core.App.createElementCopyright();
			});
			if (gem.core.App.multiThread !== undefined) {
				let scriptEle = document.createElement("script");
				scriptEle.setAttribute("src", baseURL + 'sdk/js/TutorialAppSDLdebugMulti020022.js');
				scriptEle.setAttribute("type", "text/javascript");
				scriptEle.setAttribute("async", true);
				document.body.appendChild(scriptEle);
			} else {
				if (gem.core.App.enableDebug !== undefined && gem.core.App.enableDebug === true)
					fetch(baseURL + 'sdk/js/' + SDK_version + '/MagicEarthdebug_0001.js').then((response) => response.arrayBuffer())
						.then((bytes) => {
							Module['javscriptFile'] = bytes;
							gem.core.App.addScriptToDom(bytes)
						});
				else
					fetch(baseURL + 'sdk/js/' + SDK_version + '/MagicEarthrelease_0001.js').then((response) => response.arrayBuffer())
						.then((bytes) => {
							Module['javscriptFile'] = bytes;
							gem.core.App.addScriptToDom(bytes)
						});
			}
		const baseURI = baseURL + "sdk/js/";
		let worldMapFile = baseURI + 'WM_7_403.map';
		let n = worldMapFile.lastIndexOf("/");
		let filename = worldMapFile.substring(n);
		//gem.core.App._createFile("/Data/Res", filename, baseURI + "res/map/latest/", true, true);
		//gem.core.App._createFile("/Data/Res/Renderer", 'Font-2.sys', baseURI + 'AppData/Data/Res/Renderer/Font-2.sys', true, true);
		//gem.core.App._createFile("/Data/Res", 'Countries.proto', baseURI + 'res/lang/generic/Countries.proto', true, true);

		} catch (err) {
			console.log(err);
			//window.location.reload(true);
		}
		if (initOptions && initOptions.container) {
			gem.core.App.defaultAppScreen = new gem.core.AppScreen(initOptions.container);
			return gem.core.App.defaultAppScreen;
		}
	}

	/**
	 * returns the default Screen
	 * @returns {gem.d3Scene.Screen}      default Screen
	 */
	static getDefaultScreen() {
		return new gem.d3Scene.Screen(Module.getDefaultScreen());
	}
	/**
	 * Get Last Known GPS position
	 * @returns {gem.core.Coordinates}
	 */
	static getLastKnownPosition() {
		return Module.getLastKnownPosition();
	}
	static initializeStructures() {
		gem.core.EAddressDetailLevel.initializeData();
		gem.routesAndNavigation.ERouteType.initializeData();
		gem.routesAndNavigation.ERouteTransportMode.initializeData();
		gem.d3Scene.EMarkerType.initializeData();
		gem.d3Scene.EUsableIcons.initializeData();
		gem.d3Scene.ECursorEvent.initializeData();
		gem.d3Scene.EHighlightOptions.initializeData();
		gem.routesAndNavigation.ERoadType.initializeData();
		gem.routesAndNavigation.ESurfaceType.initializeData();
		gem.content.EContentType.initializeData();
		gem.routesAndNavigation.ERouteResultDetails.initializeData();
		gem.routesAndNavigation.ERoutePathAlgorithm.initializeData();
		gem.routesAndNavigation.EBikeProfile.initializeData();
		gem.routesAndNavigation.EEBikeType.initializeData();
		gem.ETouchGestures.initializeData();
		gem.routesAndNavigation.EFuelType.initializeData();
	}
	static parseParameters(routeCallback) {
		if (gem.core.App.token !== undefined) {
			let defaultView = gem.core.App.getDefaultScreen().getDefaultMapView();
			const searchString = window.location.search;
			const urlParams = new URLSearchParams(searchString);
			let hasLat = urlParams.has("lat");
			let hasLon = urlParams.has("lon");
			//let hasZ = urlParams.has("z");
			let hasAlt = urlParams.has("alt");
			if (hasLat || hasLon) {
				let coordinates = defaultView.getCursorPositionWGS();
				coordinates.latitude = hasLat ? parseFloat(urlParams.get("lat")) : coordinates.latitude;
				coordinates.longitude = hasLon ? parseFloat(urlParams.get("lon")) : coordinates.longitude;
				coordinates.altitude = hasAlt ? parseFloat(urlParams.get("alt")) : coordinates.altitude;
				defaultView.centerOnCoordinates(coordinates, 0);
			}

			//defaultView.setZoomLevel(hasZ?parseFloat(urlParams.get("z")):zoomLevel);

			if (routeCallback !== undefined) {
				let allWp = urlParams.getAll("wp");
				if (allWp.length > 0) {
					let waypoint = new Array();
					for (let wp of allWp) {
						let coordinates = {
							latitude: 0,
							longitude: 0,
							bearing: 0,
							altitude: 0
						};
						let splitwp = wp.split(',');
						coordinates.latitude = parseFloat(splitwp[0]);
						coordinates.longitude = parseFloat(splitwp[1]);
						waypoint.push(coordinates);
					}
					routeCallback(waypoint);
				}

			}
			if (urlParams.has("geoLocation")) {
				let geoLocation = urlParams.get("geoLocation");
				geoLocation = geoLocation.replace("geo:", "");
				let pSplit = geoLocation.split(";");
				if (pSplit.length > 0) {
					let splitwp = pSplit[0].split(',');
					let coordinates = defaultView.getCursorPositionWGS();
					coordinates.latitude = splitwp[0];
					coordinates.longitude = splitwp[1];
					coordinates.altitude = 500;
					defaultView.centerOnCoordinates(coordinates, 0);
				}
			}
			if (urlParams.has("fav")) {
				let featureCollection = {
					type: "FeatureCollection",
					features: []
				};
				let allFav = urlParams.getAll("fav");
				for (let fav of allFav) {
					let parsedFav = JSON.parse(gem.Helper.generateDecodedGeoJsonPoint(fav));
					featureCollection.features.push(parsedFav);
				}
				gem.core.featureCollection = (JSON.stringify(featureCollection));
			}
		}
	}
	static setGeoJsonCallback(jsCallback) {
		gem.core.geojsoncallback = jsCallback;
	}
	/**
	 * Register Connection status changed callback
	 * @param {*} callback 
	 */
	static registerConnectionStatusChanged(callback) {
		Module.registerConnectionStatusChanged(callback);
	}
	static enableLogging(bValue) {
		if (bValue)
			Module.print = function (text) {
				text = Array.prototype.slice.call(arguments).join(' ');
				console.log(text);
			};
		else
			Module.print = (function () { })();
	}
	/**
	 * Returns bounding box for an array of coordinates
	 * @param {Array.<gem.core.Coordinates>} coordinatesArray
	 * @returns {gem.core.BoundingBox}
	 */
	static getBoundingBoxForCoordinates(coordinatesArray) {
		return Module.getBoundingBoxFromCoordinatesArray(coordinatesArray);
	}
	/**
	 * 
	 * @param {gem.core.Coordinates} coordinate1 
	 * @param {gem.core.Coordinates} coordinate2 
	 * @returns {number} - returns distance between the 2 coordinates
	 */
	static getDistanceBetween2Coordinates(coordinate1, coordinate2) {
		return Module.computeDistanceBetween2Points(coordinate1, coordinate2);
	}

	/**
	 * 
	 * @param {Array.<Byte>} image_data - canvas image data
	 * @param {number} width - width of canvas
	 * @param {number} height - height of canvas
	 * @param {object} canvas - canvas element 
	 */
	static setCanvas(image_data, width, height, canvas) {
		if (canvas) {
			var imageData = new ImageData(new Uint8ClampedArray(image_data), width, height);
			canvas.width = width;
			canvas.height = height;
			canvas.getContext('2d').putImageData(imageData, 0, 0);
		} else {
			console.error('Canvas not valid');
		}
	}
/**
 * Retrieves the current language setting.
 * @returns {string} The current language.
 */
static getLanguage() {
    return Module.getLanguage();
}

/**
 * Checks if location access is granted.
 * @param {Function} funcReturnError - Callback function to handle error.
 */
static checkLocationAccess(funcReturnError) {
    function showPosition(position) { };
    if (funcReturnError !== undefined) {
        navigator.geolocation.getCurrentPosition(showPosition, funcReturnError);
    }
}

/**
 * Requests access to location services.
 */
static requestLocationAccess() {
    Module.requestLocationAccess();
}

/**
 * Stops access to location services.
 */
static stopLocationAccess() {
    Module.stopLocationAccess();
}

/**
 * Retrieves the default scene.
 * @returns {object} The default scene.
 */
static getDefaultScene() {
    return Module.getDefaultScene();
}

/**
 * Creates a new camera for the given scene.
 * @param {object} scene - The scene for which to create a camera.
 * @returns {Module.MapCamera} The created camera.
 */
static createCamera(scene) {
    return new Module.MapCamera(scene);
}

/**
 * Retrieves the image of the country flag based on the ISO code.
 * @param {string} isoCode - The ISO code of the country.
 * @param {gem.core.BitmapContainer} bitmap - The output bitmap container for the flag image. 
 */
static getCountryFlagImage(isoCode, bitmap) {
    Module.getCountryFlagImage(isoCode, bitmap.getRawPointer());
}

/**
 * Retrieves the version of the world map.
 * @returns {string} The version of the world map.
 */
static getWorldMapVersion() {
    return Module.getWorldMapVersion();
}

/**
 * Sets a custom URL for a specific service.
 * @param {string} serviceCode - The code of the service.
 * @param {string} customURL - The custom URL to set.
 */
static setCustomUrl(serviceCode, customURL) {
	Module.setCustomUrl(serviceCode, customURL);
}
}
/**
 * @class gem.Helper
 * @memberof gem
 * @hideconstructor
 */
gem.Helper = class helper {
	/**
	 * Create a string, ready to be added to the send url as a Waypoint parameter.
	 * @param {gem.core.Coordinates} coordinates
	 * @returns {string} string ready for url. 
	 */
	static createWaypointString(coordinates) {
		return "wp=" + coordinates.latitude + "," + coordinates.longitude;
	}
	/**
	 * 
	 * @param {gem.core.Coordinates} coordinatesI - coordinates of the item 
	 * @param {string} properties - json properites of the item 
	 * @returns {string} - returns encoded geojson item to be added to the send url
	 */
	static generateEncodedGeoJsonPoint(coordinatesI, properties) {
		let geojsonPoint = {
			type: "Feature",
			id: 0,
			geometry: {
				type: "Point",
				coordinates: [
					coordinatesI.longitude,
					coordinatesI.latitude
				]
			},
			properties: properties
		};
		let encode = encodeURIComponent(JSON.stringify(geojsonPoint));
		encode = encode.replaceAll('[', '%5B');
		encode = encode.replaceAll(']', '%5D');
		encode = encode.replaceAll('{', '%7B');
		encode = encode.replaceAll('}', '%7D');
		encode = encode.replaceAll(/"/g, '%22');
		return ("fav=" + encode);
	}
	/**
	 * Decode previously encoded geojson for url submission 
	 * @param {string} inputCodedGeoJson - encoded geojson
	 *  @returns {string} - decoded geojson
	 */
	static generateDecodedGeoJsonPoint(inputCodedGeoJson) {
		let inputReplaced = inputCodedGeoJson.replaceAll('%5B', '[');
		inputReplaced = inputReplaced.replaceAll('%5D', ']');
		inputReplaced = inputReplaced.replaceAll('%7B', '{');
		inputReplaced = inputReplaced.replaceAll('%7D', '}');
		inputReplaced = inputReplaced.replaceAll('%22', /"/g);
		let decode = decodeURIComponent(inputReplaced);
		return decode;
	}
}

/**Route Traffic Event class
 * @class gem.routesAndNavigation.RouteTrafficEvent
 * @memberof gem.routesAndNavigation
 */
gem.routesAndNavigation.RouteTrafficEvent = class routetrafficEvent extends Em_Object {
	/**
	 * Get the distance in meters from starting point on current route of the traffic event to the destination.
	 @returns {number} - distance in meters
	 */
	getDistanceToDestination() {
		return Module.RouteTrafficEvent.getDistanceToDestination(this.m_object);
	}
	/**
	 * Get the image for the traffic event
	 * @param {gem.core.BitmapContainer} bitmapContainer - output bitmap
	 */
	getImage(bitmapContainer) {
		Module.RouteTrafficEvent.getImage(this.m_object, bitmapContainer.getRawPointer());
	}
	/** Get geographical area of the traffic event
	 * @returns {gem.core.BoundingBox}
	 */
	getBoundingBox() {
		return Module.RouteTrafficEvent.getBoundingBox(this.m_object);
	}
	/**
	 *  Gets the estimated delay in seconds caused by the traffic event.
	 * @returns {number} - delay in seconds.For roadblocks it will return -1.
	 */
	getDelay() {
		return Module.RouteTrafficEvent.getDelay(this.m_object);
	}
	/**
	 * Gets start time UTC
	 * @returns {number} - Epoch time
	 */
	getStartTime() {
		return Module.RouteTrafficEvent.getStartTime(this.m_object);
	}
	/**
	 * Gets end time UTC
	 * @returns {number} - Epoch time
	 */
	getEndTime() {
		return Module.RouteTrafficEvent.getEndTime(this.m_object);
	}
	/**Get the description of the traffic event.
	 * @returns {string}
	 */
	getDescription() {
		return Module.RouteTrafficEvent.getDescription(this.m_object);
	}
	/**
	 * Get the length in meters of the road segment affected by the traffic event.
	 * @returns {number}
	 */
	getLength() {
		return Module.RouteTrafficEvent.getLength(this.m_object);
	}
	/**
	 *  Get the traffic event reference point
	 * @returns {gem.core.Coordinates} - reference point
	 */
	getReferencePoint() {
		return Module.RouteTrafficEvent.getReferencePoint(this.m_object);
	}
}

gem.routesAndNavigation.RouteTrafficEventList = class RouteTrafficEventList extends Em_Vector {
	/**
	 * Get Route Traffic event at specifed position in list
	 * @param {number} nId
	 * @returns {gem.routesAndNavigation.RouteTrafficEvent} - traffic event 
	 */
	get(nId) {
		return new gem.routesAndNavigation.RouteTrafficEvent(this.m_object.get(nId));
	}
}
/**vector class
 * @class gem.d3Scene.MarkerRef
 * @memberof gem.d3Scene
 */
gem.d3Scene.MarkerRef = class vector extends Em_Object {
	constructor(object, vectorId) {
		super(object);
		if (vectorId === undefined)
			this.m_id = Module.View.getVectorId(this.m_object);
		else
			this.m_id = vectorId;
		this.initialIndex = 0;
	}
	/**
	 * Add Point by coordinates
	 * @param {gem.core.Coordinates} coordinates
	 * @param {number} nIndex
	 * @returns {number} - index of the point
	 */
	addPoint(coordinates, nIndex) {
		return Module.View.addPoint(this.m_object, coordinates, nIndex);
	}
	/**
	 * Get id of the vector
	 * @returns {number}
	 */
	getId() {
		return this.m_id;
	}
	valueOf() {
		return this.m_id;
	}
	/**
	 * 
	 * @param {number} nId - index of coordinates part 
	 * @returns {object} - vector of coordinates
	 */
	getCoordinatesPartAt(nId) {
		return Module.View.getCoordinatesAt(this.m_object, nId);
	}
	getCoordinatesAt(nId) {
		return Module.View.getCoordinatesAt(this.m_object, nId);
	}
	/**
	 * size of the vector
	 * @returns {number} - size of the vector
	 */
	size() {
		return Module.View.getVectorSize(this.m_object);
	}
	/**
	 * get information about the vector
	 * @returns {string} - information about vector
	 */
	getInfo() {
		return Module.View.getVectorInfo(this.m_object);
	}
	/**
	 * Change position of a point
	 * @param {number} pointId - id of the point
	 * @param {gem.core.Coordinates} coordinates - coordinates where to move the point
	 */
	movePoint(pointId, coordinates) {
		Module.View.movePoint(this.m_object, pointId, coordinates);
	}
	/**
	 * Remove point at index location
	 * @param {number} nId
	 */
	removePoint(nId) {
		Module.View.removePoint(this.m_object, nId);
	}
}

/**vector class
 * @class gem.d3Scene.Marker
 * @memberof gem.d3Scene
 */
gem.d3Scene.Marker = class vector extends Em_Object {
	constructor(object, vectorId) {
		super(object);
		if (vectorId === undefined)
			this.m_id = this.m_object.getVectorId();
		else
			this.m_id = vectorId;
		this.initialIndex = 0;
	}
	/**
	 * Add Point by coordinates
	 * @param {gem.core.Coordinates} coordinates
	 * @param {number} nIndex
	 * @returns {number} - index of the point
	 */
	addPoint(coordinates, nIndex) {
		return this.m_object.addPoint(coordinates, nIndex);
	}
	/**
	 * Get id of the vector
	 * @returns {number}
	 */
	getId() {
		return this.m_id;
	}
	valueOf() {
		return this.m_id;
	}
	/**
	 * 
	 * @param {number} nId - index of coordinates part 
	 * @returns {object} - vector of coordinates
	 */
	getCoordinatesPartAt(nId) {
		return this.m_object.getCoordinatesAt(nId);
	}
	getCoordinatesAt(nId) {
		return this.m_object.getCoordinatesAt(nId);
	}
	/**
	 * size of the vector
	 * @returns {number} - size of the vector
	 */
	size() {
		return this.m_object.getPartCount();
	}
	/**
	 * get information about the vector
	 * @returns {string} - information about vector
	 */
	getInfo() {
		return this.m_object.getInfo();
	}
	/**
	 * Change position of a point
	 * @param {number} pointId - id of the point
	 * @param {gem.core.Coordinates} coordinates - coordinates where to move the point
	 */
	movePoint(pointId, coordinates) {
		this.m_object.movePoint(pointId, coordinates);
	}
	/**
	 * Remove point at index location
	 * @param {number} nId
	 */
	removePoint(nId) {
		this.m_object.removePoint(nId);
	}
}

/**MarkerCollectionRef class
 * @class gem.d3Scene.MarkerCollectionRef
 * @memberof gem.d3Scene
 */
gem.d3Scene.MarkerCollection = class markerCollection extends Em_Object {
	constructor(nId, mapView) {
		if (mapView === undefined)
			super(nId);
		else {
			super(null);
			this.nId = nId;
			this.mapView = mapView;
		}
	}
	setNid(nId) {
		this.nId = nId;
	}
	/**
	 * Create a marker with the specifed id
	 * @param {number} markerId - marker id
	 * @param {gem.core.Coordinates} coordinates - coordinates of the marker
	 * @param {number} radius - radius of the marker
	 * @returns {gem.d3Scene.Marker}
	 */
	createMarker(markerId, coordinates, radius) {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let marker = new Module.Marker(Module.View.createVector(sourceAt, markerId, coordinates === undefined ? { latitude: 0.0, longitude: 0.0, altitude: 0.0, bearing: 0.0 } : coordinates, radius === undefined ? -1.0 : radius));
			let result = new gem.d3Scene.Marker(marker);
			sourceAt.delete();
			return result;
		} else
			return new gem.d3Scene.Marker(new Module.Marker(Module.View.createVector(this.m_object, markerId, coordinates === undefined ? { latitude: 0.0, longitude: 0.0, altitude: 0.0, bearing: 0.0 } : coordinates, radius === undefined ? -1.0 : radius)));
	}
	/**
	 * Remove the marker with the id
	 * @param {number} nId - marker id
	 */
	removeMarker(nId) {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			Module.View.removeVector(sourceAt, nId);
			sourceAt.delete();
		} else {
			Module.View.removeVector(this.m_object, nId);
		}
	}
	/**
	 * Get the marker with the id
	 * @param {number} nId - marker id
	 * @returns {gem.d3Scene.MarkerRef} - marker reference
	 */
	getMarkerAt(nId) {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let item = Module.View.getVectorAt(sourceAt, nId);
			let result = Module.VectorItem.isNull(item) ? undefined : new gem.d3Scene.MarkerRef(item);
			sourceAt.delete();
			return result;
		} else {
			let item = Module.View.getVectorAt(this.m_object, nId);
			return Module.VectorItem.isNull(item) ? undefined : new gem.d3Scene.MarkerRef(item);
		}
	}
	/**
	 * Returns MarkerCollection type
	 * @returns {gem.d3Scene.EMarkerType}
	 */
	getType() {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let result = sourceAt.getType();
			sourceAt.delete();
			return result;
		} else {
			return this.m_object.getType();
		}
	}
	/**
	 * Get the name of the MarkerCollection
	 * @returns {string}
	 */
	getName() {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let result = sourceAt.getName();
			sourceAt.delete();
			return result;
		} else {
			return this.m_object.getName();
		}
	}
	/**
	 * Get Bounding Box (WGS coordinates) of the MarkerCollection
	 * @returns {gem.core.BoundingBox}
	 */
	getArea() {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let result = sourceAt.getArea();
			sourceAt.delete();
			return result;
		} else {
			return this.m_object.getArea();
		}
	}
	/**
	 * Get size
	 * @returns {number}
	 */
	size() {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let result = sourceAt.size();
			sourceAt.delete();
			return result;
		} else {
			return this.m_object.size();
		}
	}
	/**
	 * Clear MarkerCollection
	 */
	clear() {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			sourceAt.clear();
			sourceAt.delete();
		} else {
			this.m_object.clear();
		}
	}
	/**
	 *  Gets the group head marker for the given marker id
	 * @param {string} nId - marker id
	 * @returns {gem.d3Scene.MarkerRef} - If group head info is not available the function will return a reference to the queried marker
	 */
	getGroupHeadItem(nId) {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let result = new gem.d3Scene.MarkerRef(sourceAt.getGroupHeadItem(nId));
			sourceAt.delete();
			return result;
		} else {
			return new gem.d3Scene.MarkerRef(this.m_object.getGroupHeadItem(nId));
		}
	}
	getGroupItemsVector(nId) {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let result = sourceAt.getGroupItemsVector(nId);
			sourceAt.delete();
			return result;
		} else {
			return this.m_object.getGroupItemsVector(nId);
		}
	}
	getRawPointer() {
		if (this.m_object === null)
			return Module.View.getSourceAt(this.mapView, this.nId);
		else
			return this.m_object;
	}
}


/**MarkerCollectionRef class
 * @class gem.d3Scene.MarkerCollectionRef
 * @memberof gem.d3Scene
 */
gem.d3Scene.MarkerCollectionRef = class source extends Em_Object {
	constructor(nId, mapView) {
		if (mapView === undefined)
			super(nId);
		else {
			super(null);
			this.nId = nId;
			this.mapView = mapView;
		}
	}
	setNid(nId) {
		this.nId = nId;
	}
	/**
	 * Create a marker with the specifed id
	 * @param {number} markerId - marker id
	 * @param {gem.core.Coordinates} coordinates - coordinates of the marker
	 * @param {number} radius - radius of the marker
	 * @returns {gem.d3Scene.Marker} - Created marker
	 */
	createMarker(markerId, coordinates, radius) {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let marker = new Module.Marker(Module.View.createVector(sourceAt, markerId, coordinates === undefined ? { latitude: 0.0, longitude: 0.0, altitude: 0.0, bearing: 0.0 } : coordinates, radius === undefined ? -1.0 : radius));
			let result = new gem.d3Scene.Marker(marker);
			sourceAt.delete();
			return result;
		} else
			return new gem.d3Scene.Marker(new Module.Marker(Module.View.createVector(this.m_object, markerId, coordinates === undefined ? { latitude: 0.0, longitude: 0.0, altitude: 0.0, bearing: 0.0 } : coordinates, radius === undefined ? -1.0 : radius)));
	}
	/**
	 * Remove the marker with the id
	 * @param {number} nId - marker id
	 */
	removeMarker(nId) {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			Module.View.removeVector(sourceAt, nId);
			sourceAt.delete();
		} else {
			Module.View.removeVector(this.m_object, nId);
		}
	}
	/**
	 * Get the marker with the id
	 * @param {number} nId - marker id
	 * @returns {gem.d3Scene.MarkerRef} - marker reference
	 */
	getMarkerAt(nId) {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let item = Module.View.getVectorAt(sourceAt, nId);
			let result = Module.VectorItem.isNull(item) ? undefined : new gem.d3Scene.MarkerRef(item);
			sourceAt.delete();
			return result;
		} else {
			let item = Module.View.getVectorAt(this.m_object, nId);
			return Module.VectorItem.isNull(item) ? undefined : new gem.d3Scene.MarkerRef(item);
		}
	}
	/**
	 * Returns MarkerCollection type
	 * @returns {gem.d3Scene.EMarkerType}
	 */
	getType() {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let result = Module.View.getSourceType(sourceAt);
			sourceAt.delete();
			return result;
		} else {
			return Module.View.getSourceType(this.m_object);
		}
	}
	/**
	 * Get the name of the MarkerCollection
	 * @returns {string}
	 */
	getName() {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let result = Module.View.getSourceName(sourceAt);
			sourceAt.delete();
			return result;
		} else {
			return Module.View.getSourceName(this.m_object);
		}
	}
	/**
	 * Get Bounding Box (WGS coordinates) of the MarkerCollection
	 * @returns {gem.core.BoundingBox}
	 */
	getArea() {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let result = Module.View.getSourceArea(sourceAt);
			sourceAt.delete();
			return result;
		} else {
			return Module.View.getSourceArea(this.m_object);
		}
	}
	/**
	 * Get size
	 * @returns {number}
	 */
	size() {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let result = Module.View.getSourceSize(sourceAt);
			sourceAt.delete();
			return result;
		} else {
			return Module.View.getSourceSize(this.m_object);
		}
	}
	/**
	 * Clear MarkerCollection
	 */
	clear() {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			Module.View.clearSource(sourceAt);
			sourceAt.delete();
		} else {
			Module.View.clearSource(this.m_object);
		}
	}
	/**
	 *  Gets the group head marker for the given marker id
	 * @param {string} nId - marker id
	 * @returns {gem.d3Scene.MarkerRef} - If group head info is not available the function will return a reference to the queried marker
	 */
	getGroupHeadItem(nId) {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let result = new gem.d3Scene.MarkerRef(Module.View.getGroupHeadItem(sourceAt, nId));
			sourceAt.delete();
			return result;
		} else {
			return new gem.d3Scene.MarkerRef(Module.View.getGroupHeadItem(this.m_object, nId));
		}
	}
	getGroupItemsVector(nId) {
		if (this.m_object === null) {
			let sourceAt = Module.View.getSourceAt(this.mapView, this.nId);
			let result = Module.View.getGroupItemsVector(sourceAt, nId);
			sourceAt.delete();
			return result;
		} else {
			return Module.View.getGroupItemsVector(this.m_object, nId);
		}
	}
	getRawPointer() {
		if (this.m_object === null)
			return Module.View.getSourceAt(this.mapView, this.nId);
		else
			return this.m_object;
	}
}
/**MarkerCollectionRefList class
 * @class gem.d3Scene.MarkerCollectionRefList
 * @augments Em_Vector
 * @memberof gem.d3Scene
 */
gem.d3Scene.MarkerCollectionRefList = class MarkerCollectionRefList extends Em_Vector {
	constructor() {
		super(new Module.VectorDataSourceRefList());
	}
	/**
	 * Get .MarkerCollection at specified position in the list
	 * @param {number} nId 
	 * @returns {gem.d3Scene.MarkerCollectionRef}
	 */
	get(nId) {
		let retVal = new gem.d3Scene.MarkerCollection(this.m_object.get(nId));
		retVal.setNid(nId);
		return retVal;
	}
}

/**
 * @class gem.core.LandmarkCategoryList
 * @augments Em_Vector
 * @memberof gem
 */
gem.core.LandmarkCategoryList = class landmarkcategorylist extends Em_Vector {
	constructor(object) {
		if (object === undefined)
			super(new Module.vector$LandmarkCategory$());
		else
			super(object);
	}
	/**
	 * Get Landmark at specified position in list
	 * @param {number} nId - position in list 
	 * @returns {gem.core.LandmarkCategory}
	 */
	get(nId) {
		return new gem.core.LandmarkCategory(this.m_object.get(nId));
	}
	/**
	 * Add a landmark to the back of the list
	 * @param {gem.core.LandmarkCategory} landmark 
	 */
	push_back(landmark) {
		this.m_object.push_back(landmark.getRawPointer());
	}
}
/**
 * @class gem.core.LandmarkCategory
 * @augments Em_Object
 * @memberof gem
 */
gem.core.LandmarkCategory = class landmarkcategory extends Em_Object {
	constructor(object) {
		super(object);
	}
	/** Get Id of the landmark category.
	 * @returns {number}
	 */
	getId() {
		return this.m_object.getId();
	}
	/** Get Name
	 * @returns {string}
	 */
	getName() {
		return Module.LandmarkCategory.getNameAsString(this.m_object);
	}
	/**
	 * Get image for the landmark category
	 * @param {gem.core.BitmapContainer} bitmapContainer - output bitmap
	 */
	getImage(bitmapContainer) {
		Module.LandmarkCategory.getImageLandmarkCategory(this.m_object, bitmapContainer.getRawPointer());
	}
}

/**
 * @class gem.core.WikipediaContainer
 * @augments Em_Object
 */
gem.core.WikipediaContainer = class Wikipediacontainer extends Em_Object {
	/**
	 * Get Wikipedia page title
	 * @returns {string}
	 */
	getWikipediaPageTitle() {
		return this.m_object.getWikipediaPageTitle();
	}
	/**
	 * Get Wikipedia page description
	 * @returns {string}
	 */
	getWikiPageDescription() {
		return this.m_object.getWikiPageDescription();
	}
	/**
	 * Get wikipedia page language
	 * @returns {string}
	 */
	getWikiPageLanguage() {
		return this.m_object.getWikiPageLanguage();
	}
	/** Get Wikipedia images url list
	 * @returns {vector<string>}
	 */
	getWikiImagesUrl() {
		return this.m_object.getWikiImagesUrl();
	}
	/** Get Wikipedia images description list
	 * @returns {vector<string>}
	 */
	getWikiImagesDescription() {
		return this.m_object.getWikiImagesDescription();
	}
	/** Get Wikipedia image titles list
	 * @returns {vector<string>}
	 */
	getWikiImagesTitles() {
		return this.m_object.getWikiImagesTitles();
	}
	/**Get the Wikipedia page URL
	 * @returns {string}
	 */
	getWikipediaURL() {
		return this.m_object.getWikipediaURL();
	}
}
/**
 * @class gem.core.StoreLocationListenerContainer
 * 
 */
gem.core.StoreLocationListenerContainer = class StoreLocationListenerContainer {
	constructor(storeItem) {
		this.m_storeItem = storeItem;
	}
	/**
	 * 
	 * @param {*} cb 
	 */
	registerCallback(cb) {
		this.m_cb = cb;
	}
	/**
	 * 
	 * @param {number} reason 
	 */
	callback(reason) {
		if (this.mCanCallCalback) {
			this.m_cb(this.m_storeItem, reason, this.htmlElement);
		}
	}
}
gem.d3Scene.Path = class gemPath extends Em_Object {
	constructor(inputText, format) {
		super(new Module.Path(inputText, format));
	}
	getArea() {
		return this.m_object.getArea();
	}
	getCoordinates() {
		return this.m_object.getCoordinates();
	}
}
/**
 * Used for creating a custom rendering for GeoJson points added to a source
 * @class gem.d3Scene.ExternalRenderer
 * @memberof gem.d3Scene
 */
gem.d3Scene.ExternalRenderer = class ExternalRenderer {
	/**
	 * 
	 * @param {string} imgSource - path to the image source used for icons.
	 * @param {string} sname - name of the external Renderer.
	 * @param {gem.d3Scene.MapView} view - View in which we are using the external renderer.
	 * @param {string} parentDiv - the div in which to add the created html elements.
	 * @param {string} [cssClass] - Custom css class
	 * @param {object} [options] - marker render options
	 * 	 @param {string} [options.markerCssClass] - marker style class
	 *   @param {callbackMarker} [options.markerFunction]
	 *   @param {number} [options.markerWidth = 20]
	 *   @param {number} [options.markerHeight = 20]
	 *   @param {number} [options.markerHoverWidth = 25]
	 *   @param {number} [options.markerHoverHeight = 25]
	 *   @param {string} [options.markerPos='center'] - where to place the marker relative to the item coordinate, options are center, bottom-right, bottom-center, bottom-left, top-right, top-center, top-left, right-center, left-center 
	 *   @param {gem.control.markerGroupStyleType} [options.markerGroupStyle = gem.control.MarkersGroupStyleType.default] - set different marker groups style
	 *   @param {callbackMarkerGroup} [options.markerGroupFunction] - fully customize marker groups
	 */
	constructor(imgSource, sname, view, parentDiv, cssClass, options) {
		let defaultMarkerOptions = {
			markerWidth: 20,
			markerHeight: 20,
			markerHoverWidth: 25,
			markerHoverHeight: 25,
			markerPos: 'center',
			markerFunction: undefined /*function(jsonItemProperties){}*/,
			markerGroupStyle: gem.control.MarkersGroupStyleType.default,
			markerGroupFunction: undefined /*function(groupsize){}*/
		};
		this.options = Object.assign({}, defaultMarkerOptions, options);

		this.itemImgSource = imgSource;
		this.name = sname;
		this.conMouseOut = undefined;
		this.conMouseOver = undefined;
		this.conMouseClick = undefined;
		this.view = view;
		this.cssClass = (cssClass && typeof cssClass === 'string' && cssClass.length > 0) ? cssClass : 'gem-marker';
		this.vectorItems = new Map();
		this.vectorItemsCoords = new Map();
		this.vectorItemsQuery = new Map();
		this.sourceIsPending = false;
		if (parentDiv !== undefined)
			this.parentDiv = document.getElementById(parentDiv);
	}
	clear() {
		this.destroyHasBeenCalled = true;
		for (let [key, value] of this.vectorItems) {
			value.delete();


			if (this.vectorItemsQuery !== undefined && this.vectorItemsQuery.has(key)) {
				let it = this.vectorItemsQuery.get(key);
				if (it && it.mProgressListener)
					it.mProgressListener.delete();
				this.vectorItemsQuery.delete(key);
			}
			let remitem = document.getElementById(key);
			if (remitem) {
				if (this.parentDiv) {
					this.parentDiv.removeChild(remitem);
					remitem.remove();
				} else {
					document.body.removeChild(remitem);
				}
			}
		}
		for (let [key, value] of this.vectorItemsCoords) {
			let remitem = document.getElementById(key);
			if (remitem) {
				if (this.parentDiv) {
					this.parentDiv.removeChild(remitem);
					remitem.remove();
				} else {
					document.body.removeChild(remitem);
				}
			}
		}
		this.vectorItems.clear();

	}
	callback(id, item, status, groupsize, xy) {
		if (this.destroyHasBeenCalled)
			return;
		if (xy !== undefined) {
			xy.x = this.options.markerWidth;
			xy.y = this.options.markerHeight;
		}

		if (item === undefined) {
			if (this.itemRemovedCallback)
				this.itemRemovedCallback(this.name + id, item, id);

			if (this.vectorItems.has(this.name + id)) {
				let removeVector = this.vectorItems.get(this.name + id);
				removeVector.delete();
				this.vectorItems.delete(this.name + id);
			}
			if (this.vectorItemsQuery !== undefined && this.vectorItemsQuery.has(this.name + id)) {
				let it = this.vectorItemsQuery.get(this.name + id);
				if (it && it.mProgressListener)
					it.mProgressListener.delete();
				this.vectorItemsQuery.delete(this.name + id);
			}
			let remitem = document.getElementById(this.name + id);
			if (remitem) {
				if (this.parentDiv) {
					this.parentDiv.removeChild(remitem);
					remitem.remove();
				} else {
					document.body.removeChild(remitem);
				}
			}
			if (this.vectorItemsCoords && this.vectorItemsCoords.has(this.name + id)) {
				this.vectorItemsCoords.delete(this.name + id);
			}
		} else {
			let convItem = item;
				if(item instanceof Module.OverlayItem)
				 {
					convItem = new gem.d3Scene.OverlayItem(item);
				 }
			if (status === 0) {
				switch (this.options.markerGroupStyle) {
					case gem.control.MarkersGroupStyleType.circle:
						this.createCircleMarkerDiv(id, convItem, groupsize);
						break;
					case gem.control.MarkersGroupStyleType.custom:
						this.createCustomMarkerDiv(id, convItem, groupsize);
						break;
					default:
						this.createDivForButton(id, convItem, groupsize);
				}
			} else { // update
				let remitem = document.getElementById(this.name + id);
				if (!remitem)
					return;

				let coordsPoint = this.vectorItemsCoords.get(this.name + id);
				if (coordsPoint) {
					let screenCoords = this.view.transformWgsToScreen(coordsPoint);
					let pos = this.getMarkerPos(this.options.markerPos, screenCoords, this.options.markerWidth, this.options.markerHeight);
					remitem.style.transform = "translate3d(" + pos.dx + "px," + pos.dy + "px,0px)";
				} else { // remove invalid item
					if (this.parentDiv) {
						this.parentDiv.removeChild(remitem);
						remitem.remove();
					} else {
						document.body.removeChild(remitem);
					}
				}
			}
		}
	}
	onClickEvent(event) {
		event.preventDefault();
		let item = event.currentTarget;
		let vectorItem = this.vectorItems.get(item.id);
		if (vectorItem === undefined) {
			this.clickEventRequestedForId = item.id;
		} else {
			let jsonBuffer = JSON.parse(vectorItem.getInfo());
			let coords = vectorItem.getCoordinatesPartAt(0);
			let coordsPoint = coords.get(0);
			if (this.conMouseClick !== undefined) {
				this.conMouseClick(item, jsonBuffer['properties'], coordsPoint);
			}
			this.selectedItem = {
				id: item.id,
				info: jsonBuffer,
				coordinates: coordsPoint
			};
			if (this.conItemSelected)
				this.conItemSelected(item);
		}
	}

	onMouseOverEvent(event) {
		let item = event.currentTarget;
		item.style.width = this.options.markerHoverWidth + "px";
		item.style.height = this.options.markerHoverHeight + "px";
		item.style.zIndex = '4';
		if (this.conMouseOver !== undefined) {
			let vectorItem = this.vectorItems.get(item.id);
			let info;
			let storeItem;
			if (this.mStoreList !== undefined) {
				let theid = item.id;
				theid = theid.replace(this.name, "");
				storeItem = this.mStoreList[parseInt(theid)];
				if (storeItem)
					info = storeItem.getInfo();
			} else
				info = vectorItem.getInfo();
			if (info && info.length) {
				let jsonBuffer = JSON.parse(info);
				let coords = vectorItem.getCoordinatesPartAt(0);
				let coordsPoint = coords.get(0);
				this.conMouseOver(item, jsonBuffer['properties'], coordsPoint);
			} else {
				if (this.mStoreList !== undefined && storeItem !== undefined) {
					if (!this.vectorItemsQuery.has(item.id)) {
						let itemQuery = new gem.core.StoreLocationListenerContainer(storeItem);
						this.vectorItemsQuery.set(item.id, itemQuery);
						let cb = (vectorIt, reason, itemL) => {
							let jsonBuffer = JSON.parse(vectorIt.getInfo());
							let coordsPoint = vectorIt.getCoordinatesAt(0);
							this.conMouseOver(itemL, jsonBuffer['properties'], coordsPoint);
						};
						itemQuery.registerCallback(cb);
						itemQuery.mCanCallCalback = true;
						itemQuery.htmlElement = item;
						itemQuery.mProgressListener = storeItem.queryInfo(this.view, this.mStoreId, itemQuery.callback.bind(itemQuery));
					}
				}
				//vectorItem.queryInfo
			}
		}
	}
	onMouseOutEvent(event) {
		let item = event.currentTarget;
		item.style.width = this.options.markerWidth + "px";
		item.style.height = this.options.markerHeight + "px";
		item.style.zIndex = 'auto';
		if (this.conMouseOut !== undefined) {
			let itemQuery = this.vectorItemsQuery.get(item.id);
			if (itemQuery !== undefined)
				itemQuery.mCanCallCalback = false;
			this.conMouseOut(item);
		}
	}
	/**

	 *@callback callbackMouse
	  * @param {Element} item - html element of called evend
	  * @param {object} jsonData - json data of the properties field
	 * @param {gem.core.Coordinates}  coordinates - WGS Coordinates of the item.
	 */

	/**
	 * Register mouse out callback (previously was in over state)
	 * @param {callbackMouse} callback 
	 */
	registerMouseOutCallback(callback) {
		this.conMouseOut = callback.bind(this);
	}

	/** Register Mouse click event callback.
	 * @param {callbackMouse} callback 
	 */
	registerMouseClickCallback(callback) {
		this.conMouseClick = callback.bind(this);
	}
	/**
	 * @callback callbackSelected 
	 * @param {Element} - html element of selected item 
	 */

	/**
	 * Register item selected callback
	 * @param {callbackSelected} callback 
	 */
	registerItemSelectedCallback(callback) {
		this.conItemSelected = callback.bind(this);
	}
	/**
	 * Register mouse over callback.
	 * @param {callbackMouse} callback 
	 */
	registerMouseOverCallback(callback) {
		this.conMouseOver = callback.bind(this);
	}
	registerItemAddedCallback(callback) {
		this.itemAddedCallback = callback;
	}
	registerItemRemovedCallback(callback) {
		this.itemRemovedCallback = callback;
	}
	getMarkerPos(option, screenCoords, width, height) {
		let pos = {
			dx: undefined,
			dy: undefined
		};
		switch (option) {
			case 'bottom-right':
				pos.dx = screenCoords.x;
				pos.dy = screenCoords.y;
				return pos;
			case 'bottom-center':
				pos.dx = screenCoords.x - width / 2;
				pos.dy = screenCoords.y;
				return pos;
			case 'bottom-left':
				pos.dx = screenCoords.x - width;
				pos.dy = screenCoords.y;
				return pos;
			case 'top-right':
				pos.dx = screenCoords.x;
				pos.dy = screenCoords.y - height;
				return pos;
			case 'top-center':
				pos.dx = screenCoords.x - width / 2;
				pos.dy = screenCoords.y - height;
				return pos;
			case 'top-left':
				pos.dx = screenCoords.x - width;
				pos.dy = screenCoords.y - height;
				return pos;
			case 'right-center':
				pos.dx = screenCoords.x;
				pos.dy = screenCoords.y - height / 2;
				return pos;
			case 'left-center':
				pos.dx = screenCoords.x - width;
				pos.dy = screenCoords.y - height / 2;
				return pos;
			case 'center':
			default:
				pos.dx = screenCoords.x - (width / 2);
				pos.dy = screenCoords.y - (height / 2);
				return pos;
		}
	}
	getIconLocation(item, isFunction) {
		isFunction = false;
		if (typeof this.itemImgSource === 'function') {
			isFunction = true;
			return this.itemImgSource(item);
		} else
			return this.itemImgSource;
	}

	createMarkerElement(btnDiv, item, itemId, groupsize, screenCoords) {
		if (groupsize < 2 && typeof this.itemImgSource === 'function') {
			let pt = this.itemImgSource(item);
			btnDiv.classList.add(pt);
		} else
			btnDiv.classList.add(this.cssClass);
		btnDiv.style.top = "0px";
		btnDiv.style.left = "0px";
		let pos = this.getMarkerPos(this.options.markerPos, screenCoords, this.options.markerWidth, this.options.markerHeight);
		btnDiv.style.transform = "translate3d(" + pos.dx + "px," + pos.dy + "px,0px)";
		btnDiv.id = this.name + itemId;
		if (this.parentDiv !== undefined) {
			this.parentDiv.appendChild(btnDiv)
		} else
			document.body.appendChild(btnDiv);
	}

	createGroupSizeElement(btnDiv, groupsize) {
		let grpdot = btnDiv.appendChild(document.createElement('button'));
		let str = '' + groupsize;
		grpdot.style.width = 10 + 6 * (str.length - 1) + "px";
		grpdot.style.maxWidth = grpdot.style.width;
		grpdot.textContent = groupsize;

		btnDiv.addEventListener("click", this.onClickEvent.bind(this));
		btnDiv.style.pointerEvents = 'none';
	}

	async createButton(item, itemId, groupsize, screenCoords, coordsPoint) {
		let btnDiv = document.createElement("div");
		if (this.cssClass !== undefined) {
			this.createMarkerElement(btnDiv, item, itemId, groupsize, screenCoords);
		}
		if (groupsize > 1) {
			this.createGroupSizeElement(btnDiv, groupsize);
			if (this.itemAddedCallback)
				this.itemAddedCallback(this.name, item, itemId, true);
		} else {
			if (this.options.markerFunction && typeof this.options.markerFunction === 'function') {
				let itemStringInfo = item.getInfo();
				let customMarkerDiv = this.options.markerFunction(itemStringInfo);
				if (customMarkerDiv)
					btnDiv.appendChild(customMarkerDiv);
			}

			this.dispatchMarkerDivEvents(btnDiv);
			this.vectorItems.set(this.name + itemId,(item instanceof Module.Marker)?new Module.Marker(item): item);
			if (this.clickEventRequestedForId && (this.clickEventRequestedForId === (this.name + itemId))) {
				this.clickEventRequestedForId = undefined;
				btnDiv.click();
			}
			if (this.itemAddedCallback)
				this.itemAddedCallback(this.name, item, itemId, false);
		}
		this.vectorItemsCoords.set(this.name + itemId, coordsPoint);
	}
	createDivForButton(itemId, item, groupsize) {
		let coords = item.getCoordinatesPartAt(0);
		let coordsPoint = coords.get(0);
		let screenCoords = this.view.transformWgsToScreen(coordsPoint);
		this.createButton(item, itemId, groupsize, screenCoords, coordsPoint).then();
	}

	async createCircleDiv(item, itemId, groupsize, screenCoords, coordsPoint) {
		let circleDiv = document.createElement('div');
		if (this.cssClass && this.cssClass.length) {
			if (groupsize < 2 && typeof this.itemImgSource === 'function') {
				let classImg = this.itemImgSource(item);
				circleDiv.classList.add(classImg);
			} else
				circleDiv.classList.add(this.cssClass);
		} else {
			circleDiv.classList.add('gem-marker');
		}

		circleDiv.style.top = '0px';
		circleDiv.style.left = '0px';
		circleDiv.style.willChange = 'transform';
		let pos = this.getMarkerPos(this.options.markerPos, screenCoords, this.options.markerWidth, this.options.markerHeight);
		circleDiv.style.transform = "translate3d(" + pos.dx + "px," + pos.dy + "px,0px)";
		circleDiv.id = this.name + itemId;
		if (this.parentDiv !== undefined) {
			this.parentDiv.appendChild(circleDiv);
		} else
			document.body.appendChild(circleDiv);

		if (groupsize > 1) {
			circleDiv.classList = '';
			circleDiv.classList.add('gem-marker-group');
			// large group
			if (groupsize > 100) {
				circleDiv.classList.add('large');
			}
			// medium group
			else if (groupsize > 50) {
				circleDiv.classList.add('medium');
			}
			// small group
			else {
				circleDiv.classList.add('small');
			}

			let textGroupSize = circleDiv.appendChild(document.createElement('span'));
			textGroupSize.textContent = groupsize;

			circleDiv.addEventListener("click", this.onClickEvent.bind(this));
			circleDiv.style.pointerEvents = 'none';
			if (this.itemAddedCallback)
				this.itemAddedCallback(this.name, item, itemId, true);
		} else {
			if (this.options.markerFunction && typeof this.options.markerFunction === 'function') {
				let customMarkerDiv = this.options.markerFunction(item.getInfo());
				if (customMarkerDiv)
					circleDiv.appendChild(customMarkerDiv);
			}

			this.dispatchMarkerDivEvents(circleDiv);
			this.vectorItems.set(this.name + itemId,(item instanceof Module.Marker)?new Module.Marker(item):new gem.d3Scene.OverlayItem(new Module.OverlayItem(item)));

			if (this.clickEventRequestedForId && (this.clickEventRequestedForId === (this.name + itemId))) {
				this.clickEventRequestedForId = undefined;
				circleDiv.click();
			}
			if (this.itemAddedCallback)
				this.itemAddedCallback(this.name, item, itemId, false);
		}
		this.vectorItemsCoords.set(this.name + itemId, coordsPoint);
	}

	createCircleMarkerDiv(itemId, item, groupsize) {
		let coords = item.getCoordinatesPartAt(0);
		let coordsPoint = coords.get(0);
		let screenCoords = this.view.transformWgsToScreen(coordsPoint);
		this.createCircleDiv(item, itemId, groupsize, screenCoords, coordsPoint).then();
	}

	async createCustomDiv(item, itemId, groupsize, screenCoords, coordsPoint) {
		let customDiv;
		if (groupsize < 2) {
			customDiv = document.createElement('div');
			if (this.cssClass && this.cssClass.length) {
				if (typeof this.itemImgSource === 'function') {
					let classImg = this.itemImgSource(item);
					customDiv.classList.add(classImg);
				} else
					customDiv.classList.add(this.cssClass);
			} else {
				customDiv.classList.add('gem-marker');
			}

			if (this.options.markerFunction) {
				let customMarkerDiv = this.options.markerFunction(item.getInfo());
				if (customMarkerDiv)
					customDiv.appendChild(customMarkerDiv);
			}

			this.dispatchMarkerDivEvents(customDiv);
			this.vectorItems.set(this.name + itemId,(item instanceof Module.Marker)?new Module.Marker(item): new gem.d3Scene.OverlayItem(new Module.OverlayItem(item)));

			if (this.clickEventRequestedForId && (this.clickEventRequestedForId === (this.name + itemId))) {
				this.clickEventRequestedForId = undefined;
				customDiv.click();
			}
			if (this.itemAddedCallback)
				this.itemAddedCallback(this.name, item, itemId, false);
		} else {
			if (this.options.markerGroupFunction && typeof this.options.markerGroupFunction === 'function')
				customDiv = this.options.markerGroupFunction(groupsize);
			if (customDiv) {
				customDiv.addEventListener("click", this.onClickEvent.bind(this));
				customDiv.style.pointerEvents = 'none';
				if (this.itemAddedCallback)
					this.itemAddedCallback(this.name, item, itemId, true);
			}
		}

		if (customDiv) {
			customDiv.style.top = '0px';
			customDiv.style.left = '0px';
			customDiv.style.position = 'absolute';
			customDiv.style.willChange = 'transform';
			let pos = this.getMarkerPos(this.options.markerPos, screenCoords, this.options.markerWidth, this.options.markerHeight);
			customDiv.style.transform = "translate3d(" + pos.dx + "px," + pos.dy + "px,0px)";
			customDiv.id = this.name + itemId;
			if (this.parentDiv !== undefined) {
				this.parentDiv.appendChild(customDiv);
			} else
				document.body.appendChild(customDiv);
		}
		this.vectorItemsCoords.set(this.name + itemId, coordsPoint);
	}

	createCustomMarkerDiv(itemId, item, groupsize) {
		let coords = item.getCoordinatesPartAt(0);
		let coordsPoint = coords.get(0);
		let screenCoords = this.view.transformWgsToScreen(coordsPoint);
		this.createCustomDiv(item, itemId, groupsize, screenCoords, coordsPoint).then();
	}

	dispatchMarkerDivEvents(markerDiv) {
		markerDiv.addEventListener("click", this.onClickEvent.bind(this));
		markerDiv.addEventListener("mouseover", this.onMouseOverEvent.bind(this));
		markerDiv.addEventListener("mouseout", this.onMouseOutEvent.bind(this));
		markerDiv.addEventListener("wheel", function (e) {
			markerDiv.addEventListener("wheel", function (event) {
				if (Module.canvas)
					Module.canvas.dispatchEvent(new event.constructor(event.type, event));
				event.preventDefault();
				event.stopPropagation();
			});
		});
		/*markerDiv.addEventListener("mousedown", function (e) {
			markerDiv.addEventListener("mousedown", function (event) {
				event.preventDefault();
				event.stopPropagation();
				if (Module.canvas)
					Module.canvas.dispatchEvent(new event.constructor(event.type, event));
			});
		});
		markerDiv.addEventListener("mouseup", function (e) {
			markerDiv.addEventListener("mouseup", function (event) {
				event.preventDefault();
				event.stopPropagation();
				if (Module.canvas)
					Module.canvas.dispatchEvent(new event.constructor(event.type, event));
			});
		});*/
		markerDiv.addEventListener("focusout", function (event) {
			this.onMouseOutEvent.bind(this);
		});
	}
}


/**
 *@class gem.d3Scene.ExternalQueryListener
 *@memberof gem.d3Scene
 *@augments Em_Object
 */
gem.d3Scene.ExternalQueryListener = class ExternalQueryListener extends Em_Object {
	constructor(callback) {
		super(new Module.ExternalQueryListener(callback));
	}
}

/**
 *@class gem.core.StoreLocationList
 *@memberof gem.core
 * @augments Em_Object
 */
gem.core.StoreLocationList = class StoreLocationList extends Em_Object {
	constructor() {
		super(new Module.StoreLocationResult());
	}
	/**
	 * @returns {number} - the size of List
	 */
	size() {
		return Module.StoreLocationResult.getSize(this.m_object);
	}
	/**
	 * @returns {number} - Returns total number of items (including group count)
	 */
	getTotalCount() {
		return Module.StoreLocationResult.getCount(this.m_object);
	}
	/**
	 * 
	 * @param {number} nId 
	 * @returns {gem.core.StoreLocation} - Store location at given id position.
	 */
	get(nId) {
		return new gem.core.StoreLocation(Module.StoreLocationResult.getItem(this.m_object, nId));
	}
	getMarkerAt(nId) {
		return this.get(nId);
	}
	push_back(storeLocation) {
		Module.StoreLocationResult.push_back(this.m_object, storeLocation.getRawPointer());
	}
	queryInfo(pView, storeId, cb) {
		return Module.StoreLocation.queryStoreLocation(pView.getRawPointer(), storeId, this.m_object, cb);
	}
}
/**
 *@class gem.core.StoreLocation
 *@memberof gem.core
 * @augments Em_Object
 */

gem.core.StoreLocation = class StoreLocation extends Em_Object {
	/**
	 *@returns {string} - Json buffer that contains informations about Store Location
	 */
	getInfo() {
		return Module.StoreLocation.getInfo(this.m_object);
	}
	/**
	 * 
	 * @param {number} nId
	 * @returns {gem.core.Coordinates} - Coordinates for Location Store
	 */
	getCoordinatesAt(nId) {
		return Module.StoreLocation.getCoordinatesAt(this.m_object, nId);
	}
	/**
	 * @returns {number} - Store Location id
	 */
	getId() {
		return this.mId !== undefined ? this.mId : this.mId = Module.StoreLocation.getStoreLocationId(this.m_object);
	}
	/**
	 * 
	 * @param {gem.d3Scene.MapView} pView
	 * @param {number} storeId 
	 * @param {*} cb
	 * @returns {object} - Progress Listener
	 */
	queryInfo(pView, storeId, cb) {
		if (this.resultInfo === undefined) {
			this.resultInfo = new Module.StoreLocationResult();
			Module.StoreLocationResult.push_back(this.resultInfo, this.m_object);
		}
		return Module.StoreLocation.queryStoreLocation(pView.getRawPointer(), storeId, this.resultInfo, cb);
	}
	/**
	 * Delete Store Location (C++ delete function)
	 */
	delete() {
		this.m_object.delete();
		if (this.resultInfo) {
			this.resultInfo.delete();
		}
	}
}

gem.d3Scene.ExternalRendererMarkers = class externalRendererMarkers extends gem.d3Scene.ExternalRenderer {

	callback(id, item, status, groupsize, xy) {
		if (xy !== undefined) {
			xy.x = this.options.markerWidth;
			xy.y = this.options.markerHeight;
		}
		if (item === undefined) {
			let remitem = document.getElementById(this.name + id);
			if (this.itemRemovedCallback)
				this.itemRemovedCallback(this.name + id, item, id);
			if (this.vectorItems.has(this.name + id)) {
				let toRemove = this.vectorItems.get(this.name + id);
				//toRemove.delete();
				this.vectorItems.delete(this.name + id);
			}
			if (this.parentDiv) {
				let parDiv = this.parentDiv;
				parDiv.removeChild(remitem);
				remitem.remove();
			} else
				document.body.removeChild(remitem);
		} else {
			let convItem = item;
				if(item instanceof Module.OverlayItem)
				{
					convItem = new gem.d3Scene.OverlayItem(new Module.OverlayItem(item));
				}
			if (status === 0) {
				switch (this.options.markerGroupStyle) {
					case gem.control.MarkersGroupStyleType.circle:
						this.createCircleMarkerDiv(id, convItem, groupsize);
						break;
					case gem.control.MarkersGroupStyleType.custom:
						this.createCustomMarkerDiv(id, convItem, groupsize);
						break;
					default:
						this.createDivForButton(id, convItem, groupsize);
				}
			} else {
				let remitem = document.getElementById(this.name + id);
				let coordsPoint = convItem.getCoordinates(0);
				let screenCoords = this.view.transformWgsToScreen(coordsPoint);
				let pos = this.getMarkerPos(this.options.markerPos, screenCoords, this.options.markerWidth, this.options.markerHeight);
				remitem.style.transform = "translate(" + pos.dx + "px," + pos.dy + "px)";
			}
		}
	}
	createDivForButton(itemId, item, groupsize) {
		let coordsPoint = item.getCoordinates();
		let screenCoords = this.view.transformWgsToScreen(coordsPoint);
		super.createButton(item, itemId, groupsize, screenCoords, coordsPoint).then();
	}
	createCircleMarkerDiv(itemId, item, groupsize) {
		let coordsPoint = item.getCoordinates();
		let screenCoords = this.view.transformWgsToScreen(coordsPoint);
		super.createCircleDiv(item, itemId, groupsize, screenCoords, coordsPoint).then();
	}
	createCustomMarkerDiv(itemId, item, groupsize) {
		let coordsPoint = item.getCoordinates();
		let screenCoords = this.view.transformWgsToScreen(coordsPoint);
		super.createCustomDiv(item, itemId, groupsize, screenCoords, coordsPoint).then();
	}
	onClickEvent(event) {
		let item = event.currentTarget;
		let vectorItem = this.vectorItems.get(item.id);
		if (vectorItem === undefined) {
			this.clickEventRequestedForId = item.id;
		} else {
			let vectorItem = this.vectorItems.get(item.id);
			let jsonBuffer = JSON.parse(vectorItem.getInfo());
			let coordsPoint = vectorItem.getCoordinates();
			this.selectedItem = {
				id: item.id,
				info: jsonBuffer,
				coordinates: coordsPoint
			};
			if (this.conItemSelected)
				this.conItemSelected(item);
			if (this.conMouseClick !== undefined) {
				this.conMouseClick(item, jsonBuffer['properties'], coordsPoint);
			}
		}
	}

	onMouseOverEvent(event) {
		let item = event.currentTarget;
		item.style.width = this.options.markerHoverWidth + "px";
		item.style.height = this.options.markerHoverHeight + "px";
		item.style.zIndex = '4';
		if (this.conMouseOver !== undefined) {
			let vectorItem = this.vectorItems.get(item.id);
			let info;
			info = vectorItem.getPreviewData();
			if (info.length) {
				try {
					let jsonBuffer = JSON.parse(info);
					var coordsPoint = vectorItem.getCoordinates();
					if (jsonBuffer['properties'])
						this.conMouseOver(item, jsonBuffer['properties'], coordsPoint);
					else if (jsonBuffer['parameters']) {
						let jsonProps = {};
						for (const param of jsonBuffer['parameters'].keyvals) {
							jsonProps[param.key] = param.value;
						}
						this.conMouseOver(item, jsonProps, coordsPoint);
					}
				} catch (err) { }
			}
			//vectorItem.queryInfo
		}
	}
	onMouseOutEvent(event) {
		let item = event.currentTarget;
		item.style.width = this.options.markerWidth + "px";
		item.style.height = this.options.markerHeight + "px";
		item.style.zIndex = 'auto';
		if (this.conMouseOut !== undefined) {
			this.conMouseOut(item);
		}
	}
}

/**@class gem.d3Scene.Screen
 * @description Screen
 *  @memberof gem.d3Scene
 *@hideconstructor
 */
gem.d3Scene.Screen = class Screen extends Em_Object {
	/**
	 * returns the default view
	 * @returns {gem.d3Scene.MapView}      default view
	 */
	getDefaultMapView() {
		return new gem.d3Scene.MapView(Module.getDefaultView());
	}
	/**
	 * Toggle Full Screen
	 */
	toggleFullScreen() {
		Module.toggleFullScreen();
	}
	/**
	 * Get Screen Viewport
	 * @returns {gem.core.RectangleInteger}
	 */
	getViewport() {
		return this.m_object.getViewport();
	}
	/**
	 * Create  a view in screen at the specified position and size
	 * @param {gem.core.RectangleFloat} rectangular - use normalized values from screen size. Valid values 0.0 -> 1.0;
	 * @returns {gem.d3Scene.MapView} - created MapView 
	 */
	createView(rectangular) {
		return new gem.d3Scene.MapView(new Module.View(rectangular));
	}
	/**
	 * Resize the screen
	 * @param {number} width - Width in pixels
	 * @param {number} height - Height in pixels
	 */
	resize(width, height) {
		Module.resizeScreen(width, height);
	}
}

gem.core.GemStringList = class GemStringList extends Em_Vector {
	constructor() {
		super(new Module.vector$gemString$());
	}
	push_back(item) {
		if (typeof (item) === 'string')
			this.m_object.push_back(Module.createGemString(item));
		else
			this.m_object.push_back(item);
	}
	size() {
		return this.m_object.size();
	}
}
/**
 * @class gem.core.StoreLocationLang 
 * @hideconstructor
 */
gem.core.StoreLocationLang = class StoreLocationLang extends Em_Object {
	constructor(object) {
		super(object);
		this.lang = Module.View.getStoreLocationLangLang(this.m_object);
	}
	/**
	 * @returns {number}
	 */
	getCount() {
		return Module.View.getStoreLocationLangCount(this.m_object);
	}
	/**
	 * @returns {string}
	 */
	getLangAsString() {
		return Module.View.getStoreLocationLangString(this.m_object);
	}
	/**
	 * Get Formatted Text
	 * @returns {string} - formatted text
	 */
	getFormattedText() {
		return this.getLangAsString();
	}
	getValue() {
		return this.getLangAsString();
	}
}

/**
 * @class gem.core.StoreLocationLangList
 * @hideconstructor
 */
gem.core.StoreLocationLangList = class StoreLocationLangList extends Em_Object {
	constructor() {
		super(new Module.StoreLocationConfigurationList());
	}
	push_back(nItem) {
		Module.StoreLocationConfigurationList.push_back(this.m_object, nItem);
	}
	/**
	 * 
	 * @param {number} nId 
	 * @returns {gem.core.StoreLocationLang} - StoreLocation Langs
	 */
	get(nId) {
		return new gem.core.StoreLocationLang(Module.StoreLocationConfigurationList.get(this.m_object, nId));
	}
	/**
	 *@returns {number}
	 */
	size() {
		return Module.StoreLocationConfigurationList.size(this.m_object)
	}
}
/**
 *@namespace gem.control
 */
gem.control = class control {

}
gem.control.TConntrolExecuteType = Object.freeze({
	NONE: 0,
	STYLE: 1,
	ONCONNECTED: 2
});

gem.control.TConntrolItemUpdateMode = Object.freeze({
	LIST: 0,
	INDIVIDUAL: 1
});
gem.control.LISTSORTMODE = Object.freeze({
	DISTANCE: 0,
	ALPHABETICAL: 1
});
// z-index levels for various controls
gem.control.zIndex = Object.freeze({
	none: 'auto',
	markerSelected: 9,
	markerHover: 10
});
// External Renderer Grouped Markers Predefined Styles
/**
 * @property {number} gem.control.MarkersGroupStyleType.default - default style with group size displayed in the bottom right corner of the icon
 * @property {number} gem.control.MarkersGroupStyleType.circle - style the marker groups with circles with the group size displayed in the center
 * @property {number} gem.control.MarkersGroupStyleType.custom - style the marker groups with custom rules
 */
gem.control.MarkersGroupStyleType = Object.freeze({
	default: 0,
	circle: 1,
	custom: 2
});

gem.routesAndNavigation.PTRouteSegment = class PTRouteSegment extends Em_Object {
	/**
	 * Get Name
	 * @returns {string}
	 */
	getName() {
		return this.m_object.getName();
	}
	/**
	 * Get Platform Code
	 *@returns {string}
	 */
	getPlatformCode() {
		return this.m_object.getPlatformCode();
	}
	/**
	 * Get Arrival Time
	 * @returns {Date}
	 */
	getArrivalTime() {
		return this.m_object.getArrivalTime();
	}
	/**
	 * Get Departure Time
	 * @returns {Date}
	 */
	getDepartureTime() {
		return this.m_object.getDepartureTime()
	}
	/**
	 * Get Wheelchair Support
	 * @returns {boolean}
	 */
	getHasWheelchairSupport() {
		return this.m_object.getHasWheelchairSupport();
	}
	/**
	 *Get short name
	 @returns {string}
	 */
	getShortName() {
		return this.m_object.getShortName();
	}
	/**
	 *  Get Route URL
	 * @returns {string}
	 */
	getRouteUrl() {
		return this.m_object.getRouteUrl();
	}
	/**
	 * Get Agency Name
	 * @returns {string}
	 */
	getAgencyName() {
		return this.m_object.getAgencyName();
	}
	/**
	 * Get Agency Phone number
	 * @returns {string}
	 */
	getAgencyPhone() {
		return this.m_object.getAgencyPhone();
	}
	/**
	 * Get Agency URL
	 * @returns {string}
	 */
	getAgencyUrl() {
		return this.m_object.getAgencyUrl();
	}
	/**
	 *Get Agency Fare URL
	 @returns {string}
	 */
	getAgencyFareUrl() {
		return this.m_object.getAgencyFareUrl();
	}
	/**
	 * Get Line From
	 * @returns {string}
	 */
	getLineFrom() {
		return this.m_object.getLineFrom();
	}
	/**
	 *Get Line Towards
	 *@returns {string}
	 */
	getLineTowards() {
		return this.m_object.getLineTowards();
	}
	/**
	 * Get Arrival Delay in seconds
	 * @returns {number}
	 */
	getArrivalDelayInSeconds() {
		return this.m_object.getArrivalDelayInSeconds();
	}
	/**
	 * Get Departure Delay in seconds
	 * @returns {number}
	 */
	getDepartureDelayInSeconds() {
		return this.m_object.getDepartureDelayInSeconds();
	}
	/**
	 * Get Bicycle Support
	 * @returns {boolean}
	 */
	getHasBicycleSupport() {
		return this.m_object.getHasBicycleSupport();
	}
	/**
	 * Get Stay on same Transit
	 * @returns {boolean}
	 */
	getStayOnSameTransit() {
		return this.m_object.getStayOnSameTransit();
	}
	/**
	* Get Transit Type
	* @returns {number}
	 */
	getTransitType() {
		return this.m_object.getTransitType();
	}
	/**
	 *Get Realtime Status
	 @returns {string} - Realtime Status
	 */
	getRealtimeStatus() {
		return this.m_object.getRealtimeStatus();
	}
	/**
	 * Get Line Block ID
	 * @returns {number}
	 */
	getLineBlockID() {
		return this.m_object.getLineBlockID();
	}
	/**
	 * Get Line Color
	 * @returns {gem.core.ColorInfo}
	 */
	getLineColor() {
		return this.m_object.getLineColor();
	}
	/**
	 * Get Line Text Color
	 * @returns {gem.core.ColorInfo}
	 */
	getLineTextColor() {
		return this.m_object.getLineTextColor();
	}
	/**
	 * Get number of alerts for this PT information.
	 * @returns {number}
	 */
	getCountAlerts() {
		return this.m_object.getCountAlerts();
	}
	/**
	 * Get alert specified by index.
	 * @param {number} nIndex - index of alert
	 * @returns {gem.core.Alert}
	 */
	getAlert(nIndex) {
		return this.m_object.getAlert(nIndex);
	}
	/**
	 * Return true if this is significant. (worth showing information about it)
	 * @returns {boolean}
	 */
	isSignificant() {
		return this.m_object.isSignificant();
	}
	/**
	 * Check if this is a station walk.
	 * @returns {boolean}
	 */
	isStationWalk() {
		return this.m_object.isStationWalk();
	}
}
/**
 * @class gem.routesAndNavigation.RouteSegment
 */
gem.routesAndNavigation.RouteSegment = class RouteSegment extends Em_Object {
	/**
	 * @returns {gem.routesAndNavigation.PTRouteSegment} ptRouteSegment
	 */
	toPTRouteSegment() {
		return new gem.routesAndNavigation.PTRouteSegment(this.m_object.toPTRouteSegment());
	}
	isCommon() {
		return this.m_object.isCommon();
	}
	getTimeDistance() {
		return this.m_object.getTimeDistance();
	}
}
gem.routesAndNavigation.RouteSegmentList = class RouteSegmentList extends Em_Vector {
	get(position) {
		return new gem.routesAndNavigation.RouteSegment(this.m_object.get(position));
	}
}
gem.core.LandmarkStore = class LandmarkStore extends Em_Object {
	constructor(name) {
		super(new Module.LandmarkStore(name));
	}
	getName() {
		return this.m_object.getName();
	}
	addLandmark(landmark, categoryId) {
		this.m_object.addLandmark(landmark.getRawPointer(), categoryId ? categoryId : -2);
		if (gem.core.App.usePersistentStorage)
			gem.core.App.syncStorageLandmarks(false);
	}
	getLandmarks(categoryId) {
		return new gem.core.LandmarkList(this.m_object.getLandmarks(categoryId ? categoryId : -2));
	}
	getLandmarksInArea(geographicArea, categoryId) {
		return new gem.core.LandmarkList(this.m_object.getLandmarksInArea(geographicArea.getRawPointer(), categoryId ? categoryId : -2));
	}
	removeLandmark(landmark) {
		this.m_object.removeLandmark(landmark.getLandmarkId());
		if (gem.core.App.usePersistentStorage)
			gem.core.App.syncStorageLandmarks(false);
	}
	removeAllLandmarks() {
		this.m_object.removeAllLandmarks();
		if (gem.core.App.usePersistentStorage)
			gem.core.App.syncStorageLandmarks(false);
	}
	startFastUpdateMode() {
		this.m_object.startFastUpdateMode();
	}
	stopFastUpdateMode() {
		this.m_object.stopFastUpdateMode();
	}
}
gem.control.BaseControl = class BaseControl {

	constructor(whenToTriggerInit) {
		this.mType = whenToTriggerInit;
	}
	initControl(parentDiv, mapView) {

	}
	cameraChangedCallback() {

	}
	preRenderCallback() {

	}
	getType() {
		return this.mType;
	}
	show() {

	}
	hide() {

	}
}

/**
 * Control for drawing an area on the map canvas
 * @class gem.control.DrawAreaOnMapControl
 * 
 * @param {gem.control.CustomQueryAddedDataControl} addedDataControl
 * @param {object} drawOptions - draw area control options  
 *   @param {object} drawOptions.button - start/stop drawing button options
 *     @param {string} drawOptions.button.parentContainer - the parent element id where to add the draw button element
 *     @param {string} [drawOptions.button.activateText = 'Draw area on map'] - button text to display for starting a draw area operation
 *     @param {string} [drawOptions.button.deactivateText = 'Stop drawing'] - button text to display for stopping a draw area operation
 *     @param {string} [drawOptions.button.cssClass = 'gem-draw-button'] - set custom style rules for the start/stop draw button
 *   @param {object} [drawOptions.dialogPopup] - dialog popup for invalid area options
 *     @param {string} [drawOptions.dialogPopup.cssClass = 'gem-dialog-popup'] - set custom style rules for the dialog
 *     @param {string} [drawOptions.dialogPopup.textInfo = 'Please draw an area'] - text to display in the dialog
 *     @param {string} [drawOptions.dialogPopup.cssDismissButton = 'gem-draw-button'] - set custom style rules for the dismiss dialog button
 *     @param {string} [drawOptions.dialogPopup.dismissText = 'Ok'] - text to display in the dismiss dialog button
 *   @param {object} [drawOptions.drawStyle] - specify custom drawing options for the area
 *     @param {number} [drawOptions.drawStyle.lineWidth = 3] - set custom line width in px
 *     @param {string} [drawOptions.drawStyle.lineCap = 'round'] - other options are 'butt' or 'square'
 *     @param {string} [drawOptions.drawStyle.lineJoin = 'round'] - other options are 'bevel' or 'miter'
 *     @param {string} [drawOptions.drawStyle.stroke = 'rgba(55, 71, 79, 0.9)'] - set custom stroke style
 *     @param {string} [drawOptions.drawStyle.fill = 'rgba(129, 156, 169, 0.4)'] - set custom fill style
 *   @param {object} [drawOptions.areaData] - options for data source inside the area
 *     @param {number} [drawOptions.areaData.maxCoords = 900] - specify maximum number of coordinates to collect from the drawing
 *     @param {number} [drawOptions.areaData.markerGroupMaxLevel = 13] - specify markers maximum grouping level
 */

gem.control.DrawAreaOnMapControl = class DrawAreaOnMapControl extends gem.control.BaseControl {
	constructor(addedDataControl, drawOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);
		this.addedDataControl = addedDataControl;
		this.mouseDown = this.mouseDown.bind(this);
		this.mouseMove = this.mouseMove.bind(this);
		this.mouseUp = this.mouseUp.bind(this);
		let defaultOptions = {
			button: {
				parentContainer: '',
				activateText: 'Draw area on map',
				deactivateText: 'Stop drawing',
				cssClass: 'gem-draw-button'
			},
			dialogPopup: {
				cssClass: 'gem-dialog-popup',
				textInfo: 'Please draw an area',
				cssDismissButton: 'gem-draw-button',
				dismissText: 'Ok'
			},
			drawStyle: {
				lineWidth: 3,
				lineCap: 'round', // other options 'butt', 'square'
				lineJoin: 'round', // other options 'bevel', 'miter'
				stroke: 'rgba(55, 71, 79, 0.9)',
				fill: 'rgba(129, 156, 169, 0.4)'
			},
			areaData: {
				maxCoords: 900,
				markerGroupMaxLevel: 13
			}
		};
		this.options = defaultOptions;
		if (drawOptions) {
			this.options = Object.assign({}, defaultOptions, drawOptions);
			if (drawOptions.button)
				this.options.button = Object.assign({}, defaultOptions.button, drawOptions.button);
			if (drawOptions.dialogPopup)
				this.options.dialogPopup = Object.assign({}, defaultOptions.dialogPopup, drawOptions.dialogPopup);
			if (drawOptions.drawStyle)
				this.options.drawStyle = Object.assign({}, defaultOptions.drawStyle, drawOptions.drawStyle);
			if (drawOptions.areaData)
				this.options.areaData = Object.assign({}, defaultOptions.areaData, drawOptions.areaData);
		}
	}

	initControl(mapContainer, mapView) {
		if (!this.addedDataControl) {
			console.log('please specify a connected data source control');
			return;
		}

		this.mapParentContainer = document.getElementById(mapContainer);
		if (!this.mapParentContainer) {
			console.log('draw controls cannot be used without map');
			return;
		}

		this.defaultMapView = mapView;
		this.drawActivate = undefined;
		this.isDrawing = undefined;
		this.screenDrawCoords = [];
		this.mapDrawCoords = [];
		this.mapDrawBoundsInit = {
			minLon: 180.0,
			minLat: 90.0,
			maxLon: -180.0,
			maxLat: -90.0
		};

		const elParent = document.getElementById(this.options.button.parentContainer);
		if (elParent)
			this.createDrawControls(elParent);
	}

	createDrawControls(parentContainer) {
		this.mapCanvas = document.getElementById('canvas');
		// draw canvas
		this.drawCanvas = this.mapParentContainer.appendChild(document.createElement('canvas'));
		this.drawCanvas.id = 'draw-canvas';
		this.drawCanvas.width = this.mapParentContainer.clientWidth;
		this.drawCanvas.height = this.mapParentContainer.clientHeight;
		this.drawCanvas.style.display = 'none';
		this.drawCanvas.style.position = 'absolute';
		this.drawCanvas.style.left = '0';
		this.drawCanvas.style.top = '0';

		this.drawContext = this.drawCanvas.getContext('2d');

		// button
		const buttonDraw = parentContainer.appendChild(document.createElement('button'));
		buttonDraw.className = this.options.button.cssClass;
		const buttonText = buttonDraw.appendChild(document.createElement('div'));
		buttonText.className = 'gem-draw-button-text';
		buttonText.textContent = this.options.button.activateText;
		const drawIcon = buttonDraw.appendChild(document.createElement('div'));
		drawIcon.classList.add('gem-draw-icon');
		const cancelDrawIcon = buttonDraw.appendChild(document.createElement('div'));
		cancelDrawIcon.classList.add('gem-cancel-draw-icon');
		cancelDrawIcon.style.display = 'none';
		cancelDrawIcon.textContent = '×';

		buttonDraw.addEventListener('click', (ev) => {
			if (this.drawActivate === undefined || this.drawActivate) {
				buttonText.textContent = this.options.button.deactivateText;
				drawIcon.style.display = 'none';
				cancelDrawIcon.style.display = '';
				this.drawActivate = false;
				this.enableDrawing();
			} else {
				buttonText.textContent = this.options.button.activateText;
				drawIcon.style.display = '';
				cancelDrawIcon.style.display = 'none';
				this.drawActivate = true;
				this.disableDrawing();
			}
		}, false);
	}

	enableDrawing() {
		this.addedDataControl.toggleCameraCallback();
		this.screenDrawCoords.length = 0;
		this.mapDrawCoords.length = 0;
		this.drawContext.clearRect(0, 0, this.drawCanvas.width, this.drawCanvas.height);

		this.drawCanvas.style.display = '';
		this.drawCanvas.style.cursor = 'crosshair';
		this.drawCanvas.style.pointerEvents = '';

		this.drawCanvas.addEventListener('mousedown', this.mouseDown, false);
		this.drawCanvas.addEventListener('mousemove', this.mouseMove, false);
		this.drawCanvas.addEventListener('mouseup', this.mouseUp, false);
		this.drawCanvas.addEventListener('touchstart', this.mouseDown, false);
		this.drawCanvas.addEventListener('touchmove', this.mouseMove, false);
		this.drawCanvas.addEventListener('touchend', this.mouseUp, false);
	}

	disableDrawing() {
		this.drawCanvas.style.display = 'none';
		this.drawCanvas.style.cursor = 'auto';

		this.drawCanvas.removeEventListener('mousedown', this.mouseDown, false);
		this.drawCanvas.removeEventListener('mousemove', this.mouseMove, false);
		this.drawCanvas.removeEventListener('mouseup', this.mouseUp, false);
		this.drawCanvas.removeEventListener('touchstart', this.mouseDown, false);
		this.drawCanvas.removeEventListener('touchmove', this.mouseMove, false);
		this.drawCanvas.removeEventListener('touchend', this.mouseUp, false);

		this.addedDataControl.toggleCameraCallback();
	}

	updateDrawBounds(mapCoord) {
		this.drawBounds.minLon = Math.min(mapCoord.longitude, this.drawBounds.minLon);
		this.drawBounds.minLat = Math.min(mapCoord.latitude, this.drawBounds.minLat);
		this.drawBounds.maxLon = Math.max(mapCoord.longitude, this.drawBounds.maxLon);
		this.drawBounds.maxLat = Math.max(mapCoord.latitude, this.drawBounds.maxLat);
	}

	zoomToDraw(buffer) {
		let viewRectangle = this.defaultMapView.getViewport();
		viewRectangle.x += buffer;
		viewRectangle.y += buffer;
		viewRectangle.width -= 2 * buffer;
		viewRectangle.height -= 2 * buffer;
		const drawLeftTop = {
			longitude: this.drawBounds.minLon,
			latitude: this.drawBounds.maxLat,
			altitude: 0,
			bearing: 0
		};
		const drawRightBottom = {
			longitude: this.drawBounds.maxLon,
			latitude: this.drawBounds.minLat,
			altitude: 0,
			bearing: 0
		};
		this.defaultMapView.centerOnArea(drawLeftTop, drawRightBottom, 0, viewRectangle);
	}
	mouseDown(e) {
		this.isDrawing = true;
		this.skipNextCoord = false;

		this.screenDrawCoords.length = 0;
		this.mapDrawCoords.lengh = 0;
		this.drawBounds = Object.create(this.mapDrawBoundsInit);
		const screenCoord = this.getScreenCoordinate(e);
		this.screenDrawCoords.push(screenCoord);
		const mapCoord = this.getMapCoordinate(screenCoord);
		this.mapDrawCoords.push(mapCoord);
		this.updateDrawBounds(mapCoord);

		this.drawCanvas.style.pointerEvents = '';
		this.drawCanvas.style.cursor = 'crosshair';
		e.preventDefault();
	}

	mouseMove(e) {
		if (this.isDrawing) {
			e.preventDefault();
			if (!this.skipNextCoord) {
				const screenCoord = this.getScreenCoordinate(e);
				this.screenDrawCoords.push(screenCoord);
				const mapCoord = this.getMapCoordinate(screenCoord);
				this.mapDrawCoords.push(mapCoord);
				this.updateDrawBounds(mapCoord);
				this.drawOnCanvas(this);
			}
			this.skipNextCoord = !this.skipNextCoord;
			// restrict coordinates length
			if (this.screenDrawCoords.length > this.options.areaData.maxCoords) {
				this.mouseUp();
			}
		}
	}

	mouseUp(e) {
		if (this.isDrawing) {
			if (e)
				e.preventDefault();
			this.isDrawing = false;
			this.screenDrawCoords.push(this.screenDrawCoords[0]);
			this.mapDrawCoords.push(this.mapDrawCoords[0]);
			this.drawOnCanvas();

			if (this.mapDrawCoords.length < 4) {
				const popup = this.createDialogPopup();
				if (popup && this.drawCanvas.parentNode)
					this.drawCanvas.parentNode.appendChild(popup);
			} else {
				this.addedDataControl.notifyAreaSelection(this.mapDrawCoords, this.options.areaData.markerGroupMaxLevel);
				this.zoomToDraw(50);
			}

			this.drawCanvas.style.cursor = 'auto';
			this.drawCanvas.style.pointerEvents = 'none';
		}
	}

	drawOnCanvas() {
		this.drawCanvas.width = this.mapParentContainer.clientWidth;
		this.drawCanvas.height = this.mapParentContainer.clientHeight;

		this.drawContext.strokeStyle = this.options.drawStyle.stroke;
		this.drawContext.lineWidth = this.options.drawStyle.lineWidth;
		this.drawContext.lineCap = this.options.drawStyle.lineCap;
		this.drawContext.lineJoin = this.options.drawStyle.lineJoin;

		this.drawContext.clearRect(0, 0, this.drawCanvas.width, this.drawCanvas.height);
		this.drawContext.beginPath();

		this.screenDrawCoords.forEach((elem, index) => {
			if (index === 0) {
				this.drawContext.moveTo(elem.x, elem.y);
			} else {
				this.drawContext.lineTo(elem.x, elem.y);
			}
		});

		if (!this.isDrawing) {
			this.drawContext.fillStyle = this.options.drawStyle.fill;
			this.drawContext.fill();
		}
		this.drawContext.stroke();
	}

	preRenderCallback() {
		if (this.drawActivate === false && this.isDrawing === false) {
			this.drawCanvas.width = this.mapParentContainer.clientWidth;
			this.drawCanvas.height = this.mapParentContainer.clientHeight;

			this.drawContext.strokeStyle = this.options.drawStyle.stroke;
			this.drawContext.lineWidth = this.options.drawStyle.lineWidth;
			this.drawContext.lineCap = this.options.drawStyle.lineCap;
			this.drawContext.lineJoin = this.options.drawStyle.lineJoin;
			this.drawContext.fillStyle = this.options.drawStyle.fill;

			this.drawContext.clearRect(0, 0, this.drawCanvas.width, this.drawCanvas.height);
			this.drawContext.beginPath();

			this.mapDrawCoords.forEach((elem, index) => {
				const screenCoord = this.defaultMapView.transformWgsToScreen(elem);
				if (index === 0) {
					this.drawContext.moveTo(screenCoord.x, screenCoord.y);
				} else {
					this.drawContext.lineTo(screenCoord.x, screenCoord.y);
				}
			});
			this.drawContext.stroke();
			this.drawContext.fill();
		}
	}

	getScreenCoordinate(e) {
		if (e.offsetX && e.offsetY) {
			return {
				x: e.offsetX,
				y: e.offsetY
			};
		} else if (e.targetTouches && e.targetTouches.length) {
			const rectangle = e.target.getBoundingClientRect();
			return {
				x: (e.targetTouches[0].clientX - rectangle.left),
				y: (e.targetTouches[0].clientY - rectangle.top)
			};
		}
	}
	getMapCoordinate(screenCoord) {
		const mapCoord = this.defaultMapView.transformScreenToWgs(screenCoord);
		mapCoord.longitude = Math.round(mapCoord.longitude * 1e5) / 1e5;
		mapCoord.latitude = Math.round(mapCoord.latitude * 1e5) / 1e5;
		return mapCoord;
	}
	createDialogPopup() {
		const popup = document.createElement('div');
		popup.className = this.options.dialogPopup.cssClass;
		const textInfo = popup.appendChild(document.createElement('p'));
		textInfo.textContent = this.options.dialogPopup.textInfo;
		const button = popup.appendChild(document.createElement('button'));
		button.className = this.options.dialogPopup.cssDismissButton;
		button.textContent = this.options.dialogPopup.dismissText;

		button.addEventListener('click', () => {
			popup.remove();
		});
		return popup;
	}
}

/**
 * Control for displaying free text search on map
 * @class gem.control.SearchControl
 * 
 * @param {object} [searchOptions]
 *    @param {object} searchOptions.highlightOptions - customize search result highlight on map options
 *       @param {bool} searchOptions.highlightOptions.showContour - default true, show search result contour if available
 *       @param {object} searchOptions.highlightOptions.contourColor - search result highlight contour color, default {r: 255, g: 0, b: 0, a: 255} 
 *       @param {bool} searchOptions.highlightOptions.overlap - default true, overlap highlight over existing map data
 *       @param {bool} searchOptions.highlightOptions.noFading - default false, disable highlight fading in / out 
 *       @param {bool} searchOptions.highlightOptions.group - default false, group landmarks
 *       @param {bool} searchOptions.highlightOptions.searchResultPin - default true - use search result pin icon, if false - highlight result landmark icon
 *    @param {object} searchOptions.searchPreferences - customize search preferences
 *       @param {bool} searchOptions.searchPreferences.exactMatch - default true
 *       @param {number} searchOptions.searchPreferences.maximumMatches - no. of search results to display, default 5, maximum 5
 *       @param {bool} searchOptions.searchPreferences.addressSearch - default false
 *       @param {bool} searchOptions.searchPreferences.mapPoisSearch - default false
 *       @param {object} searchOptions.searchPreferences.mapEnvelope - default entire map, map area in which to restrict the search results
 *          @param {gem.core.Coordinates} searchOptions.searchPreferences.mapEnvelope.topLeft - default {latitude: 90, longitude: -180, altitude: 0, bearing: 0}
 *          @param {gem.core.Coordinates} searchOptions.searchPreferences.mapEnvelope.bottomRight - default {latitude: -90, longitude: 180, altitude: 0, bearing: 0}
 *       @param {bool} searchOptions.searchPreferences.setCursorReferencePoint - default false( reference point is center of map envelope ), if set to true the search reference coordinate is set at the last cursor position
 *    	 @param {string} searchOptions.searchPreferences.placeholderText - set custom placeholder text for search box
 * 	  @param {object} searchOptions.searchResults - options for configuring the free text search results
 *       @param {bool} searchOptions.searchResults.showLandmarkIcons - default false
 *       @param {string} searchOptions.searchResults.initialPlaceSearch - default no value, specify a start-up location
 * 		 @param {number} [searchOptions.searchResults.flyToResultAltitude = 1000] - fly to result altitude in meters
 * 	  @param {object} searchOptions.cssClasses - specify custom style rules classes
 * 		 @param {string} searchOptions.cssClasses.container
 *		 @param {string} searchOptions.cssClasses.inputContainer
 *		 @param {string} searchOptions.cssClasses.inputText
 *		 @param {string} searchOptions.cssClasses.searchIcon
 *		 @param {string} searchOptions.cssClasses.searchLoader
 *		 @param {string} searchOptions.cssClasses.searchClear
 *		 @param {string} searchOptions.cssClasses.searchFocus
 *		 @param {string} searchOptions.cssClasses.searchResults
 *		 @param {string} searchOptions.cssClasses.searchResultItem
 *		 @param {string} searchOptions.cssClasses.searchResultCanvas
 *		 @param {string} searchOptions.cssClasses.searchResultContent
 *		 @param {string} searchOptions.cssClasses.searchResultTitle
 *		 @param {string} searchOptions.cssClasses.searchResultType
 */
gem.control.SearchControl = class SearchControl extends gem.control.BaseControl {
	constructor(searchOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);
		this.doneSearchTyping = 800;

		// search control option defaults
		let defaultSearchOptions = {
			highlightOptions: {
				showContour: true,
				contourColor: {
					r: 255,
					g: 0,
					b: 0,
					a: 255
				},
				overlap: true,
				noFading: false,
				group: false,
				searchResultPin: true
			},
			searchPreferences: {
				exactMatch: true,
				maximumMatches: 5,
				addressSearch: false,
				mapPoisSearch: false,
				mapEnvelope: {
					topLeft: {
						latitude: 90,
						longitude: -180,
						altitude: 0,
						bearing: 0
					},
					bottomRight: {
						latitude: -90,
						longitude: 180,
						altitude: 0,
						bearing: 0
					}
				},
				placeholderText: 'Search on map',
				setCursorReferencePoint: false
			},
			searchResults: {
				showLandmarkIcons: false,
				initialPlaceSearch: "",
				flyToResultAltitude: 1000
			},
			cssClasses: {
				container: 'gem-search-control',
				inputContainer: 'gem-search-input',
				inputText: 'gem-search-input-text',
				searchIcon: 'gem-search-icon',
				searchLoader: 'gem-search-loader',
				searchClear: 'gem-clear-search',
				searchFocus: 'gem-search-focus',
				searchResults: 'gem-search-results',
				searchResultItem: 'gem-search-result',
				searchResultCanvas: 'gem-search-result-canvas',
				searchResultContent: 'gem-search-result-content',
				searchResultTitle: 'gem-search-result-content-title',
				searchResultType: 'gem-search-result-type'
			}
		}
		this.options = defaultSearchOptions;
		if (searchOptions && searchOptions.highlightOptions)
			this.options.highlightOptions = Object.assign({}, defaultSearchOptions.highlightOptions, searchOptions.highlightOptions);
		if (searchOptions && searchOptions.searchPreferences)
			this.options.searchPreferences = Object.assign({}, defaultSearchOptions.searchPreferences, searchOptions.searchPreferences);
		if (searchOptions && searchOptions.searchResults)
			this.options.searchResults = Object.assign({}, defaultSearchOptions.searchResults, searchOptions.searchResults);
		if (searchOptions && searchOptions.cssClasses)
			this.options.cssClasses = Object.assign({}, defaultSearchOptions.cssClasses, searchOptions.cssClasses);

		if (searchOptions)
			this.selectionListeners = searchOptions.selectionListener;
		if (this.selectionListeners) {
			for (let i = 0; i < this.selectionListeners.length; i++)
				this.selectionListeners[i].setControlMode('dependend');
		}
	}

	createSearchControls(mapDiv, mapTopLeft, mapBottomRight) {
		// search controls + results container
		let searchControlsDiv = document.createElement('div');
		searchControlsDiv.id = this.options.cssClasses.container;
		searchControlsDiv.className = this.options.cssClasses.container;

		// search input container
		let searchInputDiv = searchControlsDiv.appendChild(document.createElement('div'));
		searchInputDiv.id = this.options.cssClasses.inputContainer;
		searchInputDiv.className = this.options.cssClasses.inputContainer;
		// search icon
		let searchIcon = searchInputDiv.appendChild(document.createElement('div'));
		searchIcon.className = this.options.cssClasses.searchIcon;
		// input
		let inputDiv = searchInputDiv.appendChild(document.createElement('input'));
		inputDiv.id = this.options.cssClasses.inputText;
		inputDiv.className = this.options.cssClasses.inputText;
		inputDiv.type = 'text';
		inputDiv.placeholder = this.options.searchPreferences.placeholderText;
		// search loading animation
		let searchLoader = searchInputDiv.appendChild(document.createElement('div'));
		searchLoader.id = this.options.cssClasses.searchLoader;
		searchLoader.className = this.options.cssClasses.searchLoader;
		searchLoader.style.visibility = 'hidden';
		// clear search icon
		let clearSearchDiv = searchInputDiv.appendChild(document.createElement('span'));
		clearSearchDiv.id = this.options.cssClasses.searchClear;
		clearSearchDiv.className = this.options.cssClasses.searchClear;
		clearSearchDiv.textContent = '×';
		clearSearchDiv.style.visibility = 'hidden';
		// search results
		this.searchResultsDiv = searchControlsDiv.appendChild(document.createElement('div'));
		this.searchResultsDiv.id = this.options.cssClasses.searchResults;
		this.searchResultsDiv.className = this.options.cssClasses.searchResults;
		this.searchResultsDiv.style.display = 'none';

		// onclick event listener for search icon
		searchIcon.addEventListener('click', function () {
			this.searchOnMap(inputDiv, this.searchResultsDiv, mapTopLeft, mapBottomRight, this.searchPrefs);
		}.bind(this));

		// keyup event listener for input
		inputDiv.addEventListener('keyup', function (event) {
			this.onSearchInputKeyUp(event, inputDiv, this.searchResultsDiv, mapTopLeft, mapBottomRight, this.searchPrefs);
		}.bind(this));

		// onclick event listener for clear search
		clearSearchDiv.addEventListener('click', function () {
			this.clearSearchText();
		}.bind(this));

		// add the search controls to the map
		mapDiv.appendChild(searchControlsDiv);

		// check if initial place search has been set
		if (this.options.searchResults.initialPlaceSearch.length) {
			let text = this.options.searchResults.initialPlaceSearch;
			this.searchResults = new gem.core.LandmarkList();
			let cbSearchFinished = function (reason) {
				if (this.searchResults && this.searchResults.size()) {
					let result = this.searchResults.get(0);
					this.resultOnClick(result, inputDiv);
					inputDiv.value = text;
					document.getElementById(this.options.cssClasses.searchClear).style.visibility = 'visible';
				}
			}.bind(this);
			let mapTopLeft = this.options.searchPreferences.mapEnvelope.topLeft;
			let mapBottomRight = this.options.searchPreferences.mapEnvelope.bottomRight;
			gem.places.Search.searchFor(text, mapTopLeft, mapBottomRight, cbSearchFinished, this.searchResults);
		}
	}

	initControl(container, mapView) {
		this.defaultMapView = mapView;

		// interpret search preferences
		let mapTopLeft = {
			latitude: 90,
			longitude: -180,
			altitude: 0,
			bearing: 0
		};
		let mapBottomRight = {
			latitude: -90,
			longitude: 180,
			altitude: 0,
			bearing: 0
		};
		if (this.options.searchPreferences) {
			this.searchPrefs = new gem.places.SearchPreferences();
			if (this.options.searchPreferences.mapEnvelope) {
				mapTopLeft = this.options.searchPreferences.mapEnvelope.topLeft;
				mapBottomRight = this.options.searchPreferences.mapEnvelope.bottomRight;
			}
			if (this.options.searchPreferences.exactMatch)
				this.searchPrefs.setExactMatch(this.options.searchPreferences.exactMatch);
			if (this.options.searchPreferences.maximumMatches)
				this.searchPrefs.setMaxMatches(this.options.searchPreferences.maximumMatches);
			if (this.options.searchPreferences.addressSearch)
				this.searchPrefs.setSearchAddresses(this.options.searchPreferences.maximumMatches);
			if (this.options.searchPreferences.mapPoisSearch)
				this.searchPrefs.setSearchMapPOIs(this.options.searchPreferences.mapPoisSearch);
		}
		// interpret highlight options
		this.chosenHighlightOpts = gem.d3Scene.EHighlightOptions.EHO_ShowLandmark.value; // mandatory
		if (this.options.highlightOptions.showContour)
			this.chosenHighlightOpts |= gem.d3Scene.EHighlightOptions.EHO_ShowContour.value;
		if (this.options.highlightOptions.overlap)
			this.chosenHighlightOpts |= gem.d3Scene.EHighlightOptions.EHO_Overlap.value;
		if (this.options.highlightOptions.noFading)
			this.chosenHighlightOpts |= gem.d3Scene.EHighlightOptions.EHO_NoFading;
		if (this.options.highlightOptions.group)
			this.chosenHighlightOpts |= gem.d3Scene.EHighlightOptions.EHO_Group;
		this.resultPin = undefined;
		if (this.options.highlightOptions.searchResultPin)
			this.resultPin = gem.d3Scene.EUsableIcons.eSearch_Results_Pin.value;

		let mapCanvasDiv = document.getElementById(container);
		this.createSearchControls(mapCanvasDiv, mapTopLeft, mapBottomRight);
	}
	onSearchInputKeyUp(event, searchInputDiv, searchResultsDiv, mapTopLeft, mapBottomRight, searchPreferences) {
		if (event.code === 'Enter') {
			clearTimeout(this.searchTypingTimer);
			if (this.searchResults != undefined && !this.searchResults.isDeleted() && this.searchResults.size() > 0) {
				let typedString = searchInputDiv.value.toLowerCase();
				let firstResult = this.searchResults.get(0).getName().toLowerCase();
				if (typedString && firstResult.startsWith(typedString)) {
					this.resultOnClick(this.searchResults.get(0), searchInputDiv);
					return;
				}
			}
			this.searchOnMap(searchInputDiv, searchResultsDiv, mapTopLeft, mapBottomRight, searchPreferences);
		} else {
			clearTimeout(this.searchTypingTimer);
			if (searchInputDiv.value) {
				document.getElementById(this.options.cssClasses.searchClear).style.visibility = 'visible';
				this.searchTypingTimer = setTimeout(() => {
					this.searchOnMap(searchInputDiv, searchResultsDiv, mapTopLeft, mapBottomRight, searchPreferences);
				}, this.doneSearchTyping);
			} else {
				if (!document.getElementsByClassName(this.options.cssClasses.searchResultItem))
					document.getElementById(this.options.cssClasses.searchClear).style.visibility = 'hidden';
			}
		}
	}

	searchOnMap(searchInputDiv, searchResultsDiv, mapTopLeft, mapBottomRight, searchPreferences) {
		this.clearSearchResults();
		searchInputDiv.parentElement.classList.add(this.options.cssClasses.searchFocus);

		const text = searchInputDiv.value;
		if (text == "") {
			searchInputDiv.disabled = false;
			return;
		}
		this.toggleSearchLoaderOn();

		let callbackSearchFinished = function (reason) {
			if (reason != 0) {
				this.toggleSearchLoaderOff();
				return;
			}
			this.displayResults(this.searchResults, searchInputDiv, searchResultsDiv);
		}.bind(this);

		this.searchResults = new gem.core.LandmarkList();
		if (searchPreferences) {
			if (this.options.searchPreferences.setCursorReferencePoint) {
				let cursorCoord = this.defaultMapView.getCursorPositionWGS();
				searchPreferences.setReferencePoint(cursorCoord);
			} else {
				searchPreferences.setReferencePoint({
					latitude: (mapTopLeft.latitude + mapBottomRight.latitude) / 2.0,
					longitude: (mapTopLeft.longitude + mapBottomRight.longitude) / 2.0,
					altitude: 0,
					bearing: 0
				});
			}
			gem.places.Search.searchForWithPreferences(text, mapTopLeft, mapBottomRight, callbackSearchFinished, this.searchResults, searchPreferences);
		} else
			gem.places.Search.searchFor(text, mapTopLeft, mapBottomRight, callbackSearchFinished, this.searchResults);
	}

	displayResults(resultList, searchInputDiv, searchResultsDiv) {
		this.toggleSearchLoaderOff();
		if (!searchResultsDiv)
			return;

		const resultsCount = resultList.size();
		if (resultsCount && resultsCount > 0) {
			searchResultsDiv.innerHTML = "";
			for (let i = 0; i < resultsCount; i++) {
				let result = resultList.get(i);
				// Result item
				const container = document.createElement('div');
				container.className = this.options.cssClasses.searchResultItem;
				// add mouse click event for search result item 
				container.addEventListener('click', function () {
					this.resultOnClick(result, searchInputDiv);
				}.bind(this), false);

				// search result icon ( optional )
				let imgCanvas = undefined;
				if (this.options.searchResults.showLandmarkIcons) {
					const offbitmap = new gem.core.BitmapContainer(20, 20);
					result.getImage(offbitmap);
					imgCanvas = document.createElement('canvas');
					imgCanvas.className = this.options.cssClasses.searchResultCanvas;
					imgCanvas.setAttribute('width', '20');
					imgCanvas.setAttribute('height', '20');
					imgCanvas.style.backgroundColor = 'transparent';
					gem.core.App.setCanvas(offbitmap.toImageData(), 20, 20, imgCanvas);
					offbitmap.delete();
				}

				// Result link
				const resultContent = document.createElement('div');
				resultContent.className = this.options.cssClasses.searchResultContent;

				const resultContentName = document.createElement('div');
				resultContentName.className = this.options.cssClasses.searchResultTitle;
				resultContentName.textContent = result.getFormatedName();

				const resultContentDescription = document.createElement('span');
				resultContentDescription.className = this.options.cssClasses.searchResultType;

				// details + distance from cursor position to searched location
				const distanceToNearby = gem.core.App.getDistanceBetween2Coordinates(this.defaultMapView.getCursorPositionWGS(), result.getCoordinates());
				let resultDescription = result.getFormatedDetails();
				resultDescription += this.getFormattedDistanceMetric(distanceToNearby);
				resultContentDescription.textContent = resultDescription;

				resultContent.appendChild(resultContentName);
				resultContent.appendChild(resultContentDescription);
				// Append content to item
				if (imgCanvas)
					container.appendChild(imgCanvas);
				container.appendChild(resultContent);
				// Append result to list
				searchResultsDiv.appendChild(container);
			}
		}

		// no search results
		if (resultList.size() == 0) {
			searchResultsDiv.innerHTML = "";
			const container = document.createElement('div');
			container.className = this.options.cssClasses.searchResultItem;
			const resultContent = document.createElement('div');
			resultContent.className = this.options.cssClasses.searchResultContent;
			const resultContentName = document.createElement('div');
			resultContentName.className = this.options.cssClasses.searchResultTitle;
			resultContentName.textContent = "Nothing was found";
			resultContent.appendChild(resultContentName);
			container.appendChild(resultContent);
			searchResultsDiv.appendChild(container);
		}

		searchResultsDiv.style.display = 'inline-block';
	}
	resultOnClick(pickedResult, searchInputDiv) {
		searchInputDiv.value = pickedResult.getName();

		var highlightList = new gem.core.LandmarkList();
		highlightList.push_back(pickedResult);
		this.defaultMapView.activateHighlight(highlightList, this.options.highlightOptions.contourColor, this.chosenHighlightOpts, this.resultPin);

		highlightList.delete();
		this.clearSearchResults();
		searchInputDiv.parentElement.classList.remove(this.options.cssClasses.searchFocus);

		let box = pickedResult.getBoundingBox();
		if (!box.isEmpty) {
			box.leftTop.altitude = this.options.searchResults.flyToResultAltitude;
			box.rightBottom.altitude = this.options.searchResults.flyToResultAltitude;
			this.defaultMapView.centerOnArea(box.leftTop, box.rightBottom, 0);
		} else {
			let coords = pickedResult.getCoordinates();
			coords.altitude = this.options.searchResults.flyToResultAltitude;
			this.defaultMapView.centerOnCoordinates(coords, 2000);
		}
		if (this.selectionListeners)
			this.notifySearchSelectionListeners(pickedResult);
	}
	toggleSearchLoaderOn() {
		document.getElementById(this.options.cssClasses.searchLoader).style.visibility = "visible";
	}
	toggleSearchLoaderOff() {
		document.getElementById(this.options.cssClasses.searchLoader).style.visibility = "hidden";
	}

	clearSearchResults() {
		document.getElementById(this.options.cssClasses.container).style.height = 'auto';
		document.getElementById(this.options.cssClasses.searchResults).innerHTML = "";
		document.getElementById(this.options.cssClasses.searchResults).style.display = 'none';
	}
	clearSearchText() {
		document.getElementById(this.options.cssClasses.inputText).value = '';
		document.getElementById(this.options.cssClasses.inputContainer).classList.remove(this.options.cssClasses.searchFocus);
		document.getElementById(this.options.cssClasses.searchClear).style.visibility = 'hidden';
		this.clearSearchResults();
		if (this.selectionListeners)
			this.notifySearchSelectionListeners();
		this.defaultMapView.deactivateHighlight();
		document.getElementById(this.options.cssClasses.inputText).focus();
	}

	getFormattedDistanceMetric(distance) {
		let formattedDistance = "";
		if (distance > 1000) {
			if (distance >= 10000)
				formattedDistance += " (" + (distance / 1000).toFixed(0) + "km)";
			if (distance < 10000)
				formattedDistance += " (" + (distance / 1000).toFixed(1) + "km)";
		} else {
			if (distance < 100)
				formattedDistance += " (" + this.roundToNearestMultiple(distance.toFixed(0), 5) + "m)";
			else
				formattedDistance += " (" + this.roundToNearestMultiple(distance.toFixed(0), 25) + "m)";
		}
		return formattedDistance;
	}

	roundToNearestMultiple(number, multiple) {
		// Larger multiple
		var a = Math.ceil(number / multiple) * multiple;
		// Smaller multiple
		var b = Math.floor(number / multiple) * multiple;;
		// Return of closest of two
		return (a - number > number - b) ? b : a;
	}
	notifySearchSelectionListeners(item) {
		for (let it = 0; it < this.selectionListeners.length; it++) {
			this.selectionListeners[it].notifySearchSelection(item);
		}
	}

	setLandmarkResult(landmarkresult) {
		let searchInput = document.getElementById(this.options.cssClasses.inputText);
		searchInput.value = landmarkresult.getName();
		this.resultOnClick(landmarkresult, searchInput);
	}
	hide() {
		document.getElementById(this.options.cssClasses.container).style.visibility = 'hidden';
		document.getElementById(this.options.cssClasses.inputContainer).style.visibility = 'hidden';
		document.getElementById(this.options.cssClasses.searchClear).style.visibility = 'hidden';

	}
	show() {
		document.getElementById(this.options.cssClasses.container).style.visibility = 'visible';
		document.getElementById(this.options.cssClasses.inputContainer).style.visibility = 'visible';
		document.getElementById(this.options.cssClasses.searchClear).style.visibility = 'visible';
	}
}

gem.control.NavigationControl = class NavigationControl extends gem.control.BaseControl {
	constructor(initOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);
		if (initOptions) {
			this.autoStart = initOptions.autoStart;
		}
	}
	initControl(parentDiv, mapView) {
		this.parentDiv = parentDiv;
		this.synthesis = window.speechSynthesis;
	}
	createBottomNavigationPanel() {
		this.bottomPanel = document.createElement("div");
		this.bottomPanel.classList.add("bottomPanel");
		this.bottomPanelRemainingDistance = document.createElement("div");
		this.bottomPanelRemainingDistance.classList.add("bottomPanelRemainingDistance");
		this.bottomPanelRemainingTime = document.createElement("div");
		this.bottomPanelRemainingTime.classList.add("bottomPanelRemainingTime");
		this.bottomPanel.appendChild(this.bottomPanelRemainingDistance);
		this.bottomPanel.appendChild(this.bottomPanelRemainingTime);
		document.getElementById(this.parentDiv).appendChild(this.bottomPanel);
	}

	createTopNavigationPanel() {
		this.topPanel = document.createElement("div");
		this.topPanel.style.cursor = 'pointer';
		this.topPanel.onclick = function () {
			if (this.instructionsList) {
				this.instructionsList.remove();
				this.instructionsList = undefined;
			} else {
				this.instructionsList = document.createElement("div");
				this.instructionsList.classList.add("navigationPanel");
				this.instructionsList.style.top = this.topPanel.offsetTop + this.topPanel.offsetHeight + 2 + 'px';
				this.instructionsList.style.height = '400px';
				this.instructionsList.style.overflow = 'auto';
				this.instructionsList.appendChild(this.instructionULList);
				document.getElementById(this.parentDiv).appendChild(this.instructionsList);
			}
		}.bind(this);
		this.topPanel.classList.add("navigationPanel");
		this.navigationText = document.createElement("div");
		this.navigationText.classList.add("navigationText");
		this.canvasins = document.createElement("CANVAS");

		this.canvasins.classList.add("canvasins");
		this.canvasins.style.width = "60px";
		this.canvasins.style.height = "60px";
		this.canvasins.style.float = 'left';
		this.topPanel.appendChild(this.navigationText);
		this.topPanel.appendChild(this.canvasins);
		document.getElementById(this.parentDiv).appendChild(this.topPanel);
	}
	getTrafficEvents() {
		let trafficEvents = this.route.getTrafficEvents();
		for (let i = 0; i < trafficEvents.size(); i++) {
			let trafficEvent = trafficEvents.get(i);
			console.log('Traffic Event:' + trafficEvent.getDescription());
		}
	}
	createNavInstructionList() {
		//this.getTrafficEvents();
		this.instructionULList = document.createElement("UL");
		this.instructionULList.style = 'list-style-type:none; padding-left: 0;';
		let instructionSet = this.route.getNavigationInstructionSection(0);
		for (let i = 1; i < instructionSet.size(); i++) {
			var bitmapContainer = new gem.core.BitmapContainer(30, 30);
			let instruction = instructionSet.get(i);
			instruction.getTurnImageInBitmap(bitmapContainer);
			let canvasinss = document.createElement("CANVAS");
			canvasinss.style.width = "30px";
			canvasinss.style.height = "30px";
			canvasinss.style.position = "relative";
			gem.core.App.setCanvas(bitmapContainer.toImageData(), 30, 30, canvasinss);
			var x = document.createElement("LI");
			x.setAttribute("id", "navInstructionList" + i);
			var xdiv = document.createElement("div");
			xdiv.style.display = 'flex';
			let navText = document.createElement("div");
			//navText.classList.add("navigationText");
			navText.innerHTML = '<p>' + instruction.getTurnInstructionString() + '</p>';
			xdiv.onclick = function () {
				this.mapView.centerOnRouteInstruction(instruction);
			}.bind(this);
			xdiv.onmouseover = function () {
				navText.style.backgroundColor = 'gold';
			}
			xdiv.onmouseout = function () {
				navText.style.backgroundColor = '';
			}
			xdiv.style.cursor = 'pointer';
			xdiv.appendChild(canvasinss);
			xdiv.appendChild(navText);
			x.appendChild(xdiv);
			this.instructionULList.appendChild(x);
		}
	}
	navInstructionUpdate() {
		let currentId = gem.routesAndNavigation.Navigation.getCurrentInstructionId();
		var bitmapContainer = new gem.core.BitmapContainer(60, 60);
		gem.routesAndNavigation.Navigation.getNextTurnImageInBitmap(bitmapContainer);
		gem.core.App.setCanvas(bitmapContainer.toImageData(), 60, 60, this.canvasins);
		var distancetoNext =
			gem.routesAndNavigation.Navigation.getTimeDistanceToNextTurn().totaldistance;
		var unit = " meters "
		if (distancetoNext > 1000) {
			distancetoNext = distancetoNext / 1000.0;
			unit = " Km ";
		}
		this.navigationText.innerHTML = "<p> In " + distancetoNext.toFixed(2) +
			unit + gem.routesAndNavigation.Navigation.getNextTurnInstruction() + "</p>";
		var remainedTimeDistance = gem.routesAndNavigation.Navigation.getRemainingTravelTimeDistance();
		this.bottomPanelRemainingDistance.innerHTML = (remainedTimeDistance.totaldistance / 1000).toFixed(2) + "km";
		var measuredTime = new Date(null);
		measuredTime.setSeconds(remainedTimeDistance.totaltime); // specify value in seconds
		var MHSTime = measuredTime.toISOString().substr(11, 5);
		this.bottomPanelRemainingTime.innerHTML = MHSTime + "hr";
		bitmapContainer.delete();
	}

	populateVoiceList() {
		if (typeof window.speechSynthesis === 'undefined') {
			return;
		}

		var voices = window.speechSynthesis.getVoices();

		for (var i = 0; i < voices.length; i++) {
			if (voices[i].default) {
				console.log("Voice is " + voices[i]);
			}
		}
	}
	PopulateVoicesInternal() {
		this.populateVoiceList();
		if (typeof window.speechSynthesis !== 'undefined' && window.speechSynthesis.onvoiceschanged !== undefined) {
			window.speechSynthesis.onvoiceschanged = this.populateVoiceList;
		}
	}
	startNavigation(route, mapView, notSimulation) {
		this.PopulateVoicesInternal();
		this.mapView = mapView;
		this.route = route;
		this.createNavInstructionList();
		this.navigationInstructionEM = new gem.routesAndNavigation.NavigationListener();
		let progressListener = function () {
			// Set the camera to follow the simulated position of the green arrow along the route on the map
			this.mapView.startFollowingPosition();
			this.createBottomNavigationPanel();
			this.createTopNavigationPanel();
		};
		this.navigationInstructionEM.registerNavInstructionUpdateListener(this.navInstructionUpdate.bind(this));
		if (this.synthesis)
			this.navigationInstructionEM.registerNavInstructionOnSoundListener(this.navInstructionSound.bind(this));
		if (notSimulation) {
			gem.routesAndNavigation.Navigation.startNavigation(route, progressListener.bind(this), this.navigationInstructionEM)
		} else {
			gem.routesAndNavigation.Navigation.startSimulation(route, progressListener.bind(this),
				this.navigationInstructionEM);
		}
	}
	navInstructionSound(soundString) {
		if (this.notAllowedAudio)
			return;
		let flagVoiceFirst = false;
		if (!this.firstTime) {
			let confirmId = document.createElement("DIV");
			confirmId.id = "id_confrmdiv";
			confirmId.className = "id_confrmdiv";
			let buttonTrue = document.createElement("BUTTON");
			buttonTrue.id = "id_truebtn";
			let buttonFalse = document.createElement("BUTTON");
			buttonFalse.id = "id_falsebtn";
			buttonTrue.innerHTML = "Yes";
			buttonFalse.innerHTML = "No";
			var para = document.createElement("P"); // Create a <p> element
			para.innerText = "Enable audible instructions?";
			confirmId.appendChild(para);
			confirmId.appendChild(buttonTrue);
			confirmId.appendChild(buttonFalse);
			confirmId.style.zIndex = "!999999";
			confirmId.style.display = "block";
			try {
				console.log("Added Confirm div to body");
				window.document.body.appendChild(confirmId);
			} catch (error) {
				console.log(error);
			}
			this.notAllowedAudio = true;
			buttonTrue.onclick = function () {
				let flagVoiceFirst = false;
				if (!this.voice) {
					flagVoiceFirst = true;
					let lang = window.navigator.language;
					lang = lang.replace(/[^a-zA-Z ]/g, "");
					this.voice = this.synthesis.getVoices().filter(function (voice) {
						let voicelang = voice.lang.replace(/[^a-zA-Z ]/g, "");
						console.log("The voice lang is " + voicelang + " compared to " + lang);
						return (voicelang === lang);
					})[0];
				}
				this.utterance = new SpeechSynthesisUtterance(soundString);
				this.utterance.voice = this.voice;
				this.utterance.pitch = 1.0;
				this.utterance.rate = 1.0;
				this.utterance.volume = 0.8; // Do your delete operation
				this.synthesis.speak(this.utterance);
				this.notAllowedAudio = false;
				document.getElementById("id_confrmdiv").style.display = 'none';
			}.bind(this);
			buttonFalse.onclick = function () {
				this.notAllowedAudio = true;
				document.getElementById("id_confrmdiv").style.display = 'none';
			}.bind(this);
			this.firstTime = true;
		} else {
			if (!this.voice) {
				this.synthesis = window.speechSynthesis;
				var lang = window.navigator.language;
				lang = lang.replace(/[^a-zA-Z ]/g, "");
				this.voice = this.synthesis.getVoices().filter(function (voice) {
					let voicelang = voice.lang.replace(/[^a-zA-Z ]/g, "");
					return (voicelang === lang);
				})[0];
			}
			this.utterance = new SpeechSynthesisUtterance(soundString);
			this.utterance.voice = this.voice;
			this.utterance.pitch = 1.0;
			this.utterance.rate = 1.0;
			this.utterance.volume = 0.8;
			this.synthesis.speak(this.utterance);
		}
	}

	/**
	 * Follow GPS Arrow
	 * @param {number} zoomLevel - Zoom Level
	 * @param {number} maxAngle - Max Angle
	 */
	startFollowingPosition(zoomLevel, maxAngle) {
		this.mapView.startFollowingPosition(zoomLevel, maxAngle);
	}
	/**
	 * Stop Navigation
	 */
	stopNavigation() {
		this.mapView.stopFollowingPosition();
		gem.routesAndNavigation.Navigation.stopNavigation();
		this.bottomPanel.remove();
		this.topPanel.remove();
	}
}
/**
* @callback callbackRouteSelected
* @param {number} routeId - Selected route id
* @param { gem.d3Scene.ECursorEvent} cursorEvent - Cursor Event
	 
 * @class gem.control.RouteControl
 * @memberof namespace:gem.control
 * @param {object} [initOptions] - options for calculating routes
 * @param {gem.core.Coordinates[]} initOptions.waypoints  - Waypoints
 * @param {object} [initOptions.preferences]
 *	@param {gem.routesAndNavigation.ERouteType} initOptions.preferences.routeType - route type
 *	@param {boolean} initOptions.preferences.avoidUnpavedRoads - avoid unpaved roads
 *	@param {boolean} initOptions.preferences.avoidMotorways - avoid motorways
 * 	@param {boolean} initOptions.preferences.avoidTollRoads - Avoid toll roads
 *	@param {boolean} initOptions.preferences.avoidFerries - Avoid ferries
 *  @param {boolean} initOptions.preferences.avoidTraffic - Avoid Traffic
 *	@param {number} initOptions.preferences.avoidBikingHillFactor - avoid biking hill factor  0.0 - no avoidance, 1.0 - full avoidance
 *	@param {gem.routesAndNavigation.ERouteTransportMode} initOptions.preferences.transportMode - transport mode
 * 	@param {boolean} initOptions.preferences.buildTerrainProfile - Build Terrain Profile
 *  @param {object} initOptions.preferences.routeRanges - route ranges settings
 *   @param {number[]} initOptions.preferences.routeRanges.rangelist
 *   @param {number} initOptions.preferences.routeRanges.quality 
 *  @param {object} initOptions.preferences.bikeProfile - bike profile settings
 * 	@param {gem.routesAndNavigation.ERoutePathAlgorithm} initOptions.preferences.pathAlgorithm - Path Algorithm
 *	@param {gem.routesAndNavigation.ERouteResultDetails} initOptions.preferences.resultDetails - Result Details
 * @param {gem.control.NavigationControl} navigationControl - Navigation Control Object (optional)
 * @param {callbackRouteSelected} resultCallback - route selected callback
 */


gem.control.RouteControl = class RouteControl extends gem.control.BaseControl {
	constructor(initOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);
		if (initOptions) {
			this.waypoints = new Map();
			if (initOptions.waypoints) {
				if (initOptions.waypoints.length === 1) {
					this.destinationWaypoint = initOptions.waypoints[0];
				} else {
					this.destinationWaypoint = initOptions.waypoints[initOptions.waypoints.length - 1];
					this.departureWaypoint = initOptions.waypoints[0];
				}
			}
			this.preferences = initOptions.preferences;
			this.resultCallback = initOptions.resultCallback;
			this.navigationControl = initOptions.navigationControl;
			this.activeRouteId = 0;
		}
	}
	setControlMode(mode) {
		this.controlMode = mode;
	}
	notifySearchSelection(item) {
		if (item === undefined) {
			this.clearRouteResult();
		} else {
			this.addDestination(item);
		}
	}
	routeCallback() {
		//var msecFlightDuration = 3000;
		if (this.defaultRoute.size() > this.activeRouteId) {
			// center on route
			if (this.preferences.routeRanges)
				this.mapView.centerOnRoute(this.defaultRoute.get(this.defaultRoute.size() - 1));
			else
				this.mapView.centerOnRoute(this.defaultRoute.get(this.activeRouteId));

			if (this.navigationControl && this.navigationControl.autoStart) {
				this.mapView.deregisterRouteClickedEvent();
				this.mapView.showRouteInView(this.defaultRoute.get(this.activeRouteId), true, false);
				this.navigationControl.startNavigation(this.defaultRoute.get(this.activeRouteId), this.mapView);
			} else {
				this.mapView.showRouteInView(this.defaultRoute.get(this.activeRouteId), true);
				for (let i = 0; i < this.defaultRoute.size(); i++)
					if (i !== this.activeRouteId)
						this.mapView.showRouteInView(this.defaultRoute.get(i), false);
				if (this.resultCallback) {
					this.resultCallback(this.defaultRoute);
				}
			}
		}

	}
	startNavigation(notSimulation) {
		if (this.navigationControl) {
			this.mapView.deregisterRouteClickedEvent();
			this.mapView.showRouteInView(this.defaultRoute.get(this.activeRouteId), true, false);
			for (let i = 0; i < this.defaultRoute.size(); i++)
				if (i !== this.activeRouteId)
					this.mapView.removeRoutefromView(this.defaultRoute.get(i));
			this.navigationControl.startNavigation(this.defaultRoute.get(this.activeRouteId), this.mapView, notSimulation);
		}
	}
	stopNavigation() {
		if (this.navigationControl) {
			this.mapView.removeRoutefromView(this.defaultRoute.get(this.activeRouteId));
			this.navigationControl.stopNavigation();
		}
	}
	getRouteResult() {
		return this.defaultRoute;
	}
	getTrafficEvents() {
		let trafficEvents = this.defaultRoute.get(this.activeRouteId).getTrafficEvents();
		for (let i = 0; i < trafficEvents.size(); i++) {
			let trafficEvent = trafficEvents.get(i);
			console.log('Traffic Event:' + trafficEvent.getDescription());
		}
	}
	routeSelectedCallback(nId, cursorEvent) {
		if (cursorEvent === gem.d3Scene.ECursorEvent.eCursorEvent_Down && nId !== -1) {
			this.mapView.removeRoutefromView(this.defaultRoute.get(this.activeRouteId));
			this.mapView.showRouteInView(this.defaultRoute.get(this.activeRouteId), false);
			this.activeRouteId = nId;
			this.mapView.removeRoutefromView(this.defaultRoute.get(nId));
			this.mapView.showRouteInView(this.defaultRoute.get(nId), true);
		} else {
			if (nId === -1)
				Module.setCursorStyle("")
			else
				Module.setCursorStyle("pointer")
		}
	}
	clearRouteResult() {
		if (this.defaultRoute !== undefined) {
			for (let i = 0; i < this.defaultRoute.size(); i++) {
				this.mapView.removeRoutefromView(this.defaultRoute.get(i));
			}
			this.defaultRoute.delete();
			this.defaultRoute = undefined;
		}
	}
	computeRoute() {
		this.defaultRoute = new gem.routesAndNavigation.RoutesRequest();
		if (this.preferences) {
			if (this.preferences.routeType) {
				this.defaultRoute.setRouteType((typeof this.preferences.routeType === 'function') ? this.preferences.routeType() : this.preferences.routeType);
			}
			if (this.preferences.avoidUnpavedRoads) {
				this.defaultRoute.setAvoidUnpavedRoads((typeof this.preferences.avoidUnpavedRoads === 'function') ? this.preferences.avoidUnpavedRoads() : this.preferences.avoidUnpavedRoads);
			}
			if (this.preferences.avoidMotorways) {
				this.defaultRoute.setAvoidMotorways((typeof this.preferences.avoidMotorways === 'function') ? this.preferences.avoidMotorways() : this.preferences.avoidMotorways);
			}
			if (this.preferences.avoidTollRoads) {
				this.defaultRoute.setAvoidTollRoads((typeof this.preferences.avoidTollRoads === 'function') ? this.preferences.avoidTollRoads() : this.preferences.avoidTollRoads);
			}
			if (this.preferences.avoidFerries) {
				this.defaultRoute.setAvoidFerries((typeof this.preferences.avoidFerries === 'function') ? this.preferences.avoidFerries() : this.preferences.avoidFerries);
			}
			if (this.preferences.avoidTraffic) {
				this.defaultRoute.setAvoidTraffic((typeof this.preferences.avoidTraffic === 'function') ? this.preferences.avoidTraffic() : this.preferences.avoidTraffic);
			}
			if (this.preferences.avoidBikingHillFactor) {
				this.defaultRoute.setAvoidBikingHillFactor((typeof this.preferences.avoidBikingHillFactor === 'function') ? this.preferences.avoidBikingHillFactor() : this.preferences.avoidBikingHillFactor);
			}
			if (this.preferences.transportMode) {
				this.defaultRoute.setTransportMode((typeof this.preferences.transportMode === 'function') ? this.preferences.transportMode() : this.preferences.transportMode);
			}
			if (this.preferences.buildTerrainProfile) {
				this.defaultRoute.setBuildTerrainProfile((typeof this.preferences.buildTerrainProfile === 'function') ? this.preferences.buildTerrainProfile() : this.preferences.buildTerrainProfile);
			}
			if (this.preferences.pathAlgorithm) {
				this.defaultRoute.setPathAlgorithm((typeof this.preferences.pathAlgorithm === 'function') ? this.preferences.pathAlgorithm() : this.preferences.pathAlgorithm);
			}
			if (this.preferences.resultDetails) {
				this.defaultRoute.setResultDetails((typeof this.preferences.resultDetails === 'function') ? this.preferences.resultDetails() : this.preferences.resultDetails);
			}
			if (this.preferences.routeRanges) {
				this.defaultRoute.setRouteRanges((typeof this.preferences.routeRanges === 'function') ? this.preferences.routeRanges() : this.preferences.routeRanges);
			}
			if (this.preferences.bikeProfile) {
				if (this.preferences.eBikeProfile)
					this.defaultRoute.setBikeProfileWithElectricType((typeof this.preferences.routeRanges === 'function') ? this.preferences.bikeProfile() : this.preferences.bikeProfile, (typeof this.preferences.eBikeProfile === 'function') ? this.preferences.eBikeProfile() : this.preferences.eBikeProfile);
				else
					this.defaultRoute.setBikeProfile((typeof this.preferences.routeRanges === 'function') ? this.preferences.bikeProfile() : this.preferences.bikeProfile);
			}

		}
		this.mapView.registerRouteClickedEvent(this.routeSelectedCallback.bind(this), this.defaultRoute);
		if (this.departureWaypoint) {
			this.defaultRoute.addWaypoint(this.departureWaypoint);
		} else {
			this.defaultRoute.addWaypoint(gem.core.App.getLastKnownPosition());
		}
		(this.destinationWaypoint.m_landmark === undefined) ? this.defaultRoute.addWaypoint(this.destinationWaypoint) : this.defaultRoute.addWaypointFromLandmark(this.destinationWaypoint);
		this.defaultRoute.calculateRoute(this.routeCallback.bind(this));
	}
	initControl(parentDiv, mapView) {
		this.parentDiv = parentDiv;
		this.mapView = mapView;
		if (this.controlMode === undefined)
			this.computeRoute();
	}
	addDestination(destination) {
		this.destinationWaypoint = destination;
	}
	addDeparture(departure) {
		this.departureWaypoint = departure;
	}
	addWaypoint() {

	}
	clearWaypoints() {

	}
}

/**
 * @callback callbackMarker
 * @param {string} properties - marker item properties
 */
/**
 * @callback callbackMarkerBubble
 * @param {HTMLDivElement} elMarkerBubble - html parent element
 * @param {object} properties - properties json 
 * @param {gem.core.Coordinates} coords - WGS coordinates of the marker item 
 */
/**
 * @callback callbackMarkerGroup
 * @param {number} groupsize - marker group size
 */

/** Base class for added data controls
 * @class gem.control.AddedDataControl
 * @memberof namespace:gem.control
 * 
 * @param {object} whenToTriggerInit
 * @param {object} loadingMethod
 * @param {object} [dataOptions] - options for displaying data source items
 * 	 @param {object} dataOptions.marker - options for markers display on map
 * 	   @param {string} dataOptions.marker.cssClass - specify custom marker style rules
 * 	   @param {string} dataOptions.marker.highlightClass - specify selected marker custom highlight style rules
 * 	   @param {number} [dataOptions.marker.width = 20]
 *	   @param {number} [dataOptions.marker.height = 20]
 *	   @param {number} [dataOptions.marker.hoverWidth = 25]
 *	   @param {number} [dataOptions.marker.hoverHeight = 25]
 *     @param {string} [dataOptions.marker.markerPos = 'center'] - where to place the marker relative to the item coordinate 
 *     @param {callbackMarker} dataOptions.marker.markerFunction - fully customize marker appearance  
 *   @param {object} dataOptions.markerBubble - options for styling the marker bubble on click/hover
 *     @param {string[]} dataOptions.markerBubble.title - data source properties to use for marker bubble title text
 *     @param {string} dataOptions.markerBubble.image - data source image url property to use for marker bubble image
 *     @param {number} [dataOptions.markerBubble.width = 240] - bubble width in px 
 * 	   @param {number} [dataOptions.markerBubble.height = 200] - bubble height in px
 *     @param {bool} [dataOptions.markerBubble.enableHover = true] - disable marker bubble interaction on hover
 *	   @param {bool} [dataOptions.markerBubble.enableClick = true] - disable marker bubble interaction on click
 * 	   @param {string} dataOptions.markerBubble.markerBubbleClass - custom css class for marker bubble	
 *     @param {callbackMarkerBubble} dataOptions.markerBubble.markerBubbleFunction - fully customize marker bubble
 *   @param {object} dataOptions.markerGrouping - marker grouping options
 *     @param {number} [dataOptions.markerGrouping.maxLevel = 13] - maximum map level to apply grouping ( max allowed 15 ), not available for studio data
 *	   @param {object} [dataOptions.markerGrouping.style = gem.control.MarkersGroupStyleType.default] - style to apply to marker groups	
 *     @param {callbackMarkerGroup} [dataOptions.markerGrouping.markerGroupFunction] - fully customize marker groups style
 *   @param {bool} [dataOptions.disableZoomToData = false ] - disables zooming to the data source area at start-up
 */
gem.control.AddedDataControl = class AddedDataControl extends gem.control.BaseControl {
	constructor(whenToTriggerInit, loadingMethod, dataOptions) {
		super(whenToTriggerInit);
		this.loadingMethod = loadingMethod;
		this.visibilityCallbacks = new Array();
		this.selectedItemClass = 'gem-marker-selected';
		// added data source options defaults
		let defaultOptions = {
			marker: {
				cssClass: 'gem-marker',
				highlightClass: 'gem-highlight-icon',
				width: 20,
				height: 20,
				hoverWidth: 25,
				hoverHeight: 25,
				markerPos: 'center',
				markerFunction: undefined /*function (jsonItemProperties)*/
			},
			markerBubble: {
				title: [''],
				image: [''],
				width: 240,
				height: 200,
				enableHover: true,
				enableClick: true,
				markerBubbleClass: 'gem-marker-bubble',
				markerBubbleFunction: undefined /*function (elMarkerBubble, properties, coords)*/
			},
			markerGrouping: {
				maxLevel: 13,
				style: gem.control.MarkersGroupStyleType.default,
				markerGroupFunction: undefined /*function(groupsize)*/
			},
			disableZoomToData: false
		};
		this.options = Object.assign({}, defaultOptions, dataOptions);
		if (dataOptions && dataOptions.marker)
			this.options.marker = Object.assign({}, defaultOptions.marker, dataOptions.marker);
		if (dataOptions && dataOptions.markerBubble)
			this.options.markerBubble = Object.assign({}, defaultOptions.markerBubble, dataOptions.markerBubble);
		if (dataOptions && dataOptions.markerGrouping)
			this.options.markerGrouping = Object.assign({}, defaultOptions.markerGrouping, dataOptions.markerGrouping);

		// set max grouping level to 15
		if (this.options.markerGrouping.maxLevel > 15)
			this.options.markerGrouping.maxLevel = 15;

		this.rendererOptions = {
			markerWidth: this.options.marker.width,
			markerHeight: this.options.marker.height,
			markerHoverWidth: this.options.marker.hoverWidth,
			markerHoverHeight: this.options.marker.hoverHeight,
			markerPos: this.options.marker.markerPos,
			markerFunction: this.options.marker.markerFunction,
			markerGroupStyle: this.options.markerGrouping.style,
			markerGroupFunction: this.options.markerGrouping.markerGroupFunction
		}
	}
	cameraChangedCallback() {
		if (this.mCameraCallback) {
			this.mCameraCallback();
		}
	}
	mouseClick(item, jsonObject, position) {
		// deactivate other store list items
		let activeListing = document.getElementsByClassName('active');
		if (activeListing[0]) {
			activeListing[0].classList.remove('active');
		}
	}
	mouseOver(item, jsonObject, position) {
		if (!this.options.markerBubble.enableHover)
			return;
		if (item.lastChild && item.lastChild.id === this.options.markerBubble.markerBubbleClass)
			return;
		// hide selected bubble
		if (this.selectedItem) {
			let selectedBubble = this.selectedItem.getElementsByClassName(this.options.markerBubble.markerBubbleClass);
			if (selectedBubble && selectedBubble.length)
				selectedBubble[0].style.visibility = 'hidden';
		}
		// mouse over on top of selected item
		item.style.zIndex = gem.control.zIndex.markerHover;
		if (this.options.markerBubble.markerBubbleFunction) {
			let bubble = document.createElement('div');
			bubble.id = this.options.markerBubble.markerBubbleClass;
			bubble.className = this.options.markerBubble.markerBubbleClass;
			this.options.markerBubble.markerBubbleFunction(bubble, jsonObject, position);
			item.append(bubble);
		} else {
			let bubble = this.createMarkerBubble(item, jsonObject);
			if (bubble)
				item.appendChild(bubble);
		}
	}
	mouseOut(item) {
		if (!this.options.markerBubble.enableHover)
			return;
		try {
			if (item.lastChild !== undefined && item.lastChild.classList.contains(this.options.markerBubble.markerBubbleClass))
				item.removeChild(item.lastChild);
			// remove z-index
			item.style.zIndex = gem.control.zIndex.none;
			// show selected bubble
			if (this.selectedItem) {
				let selectedBubble = this.selectedItem.getElementsByClassName(this.options.markerBubble.markerBubbleClass);
				if (selectedBubble && selectedBubble.length)
					selectedBubble[0].style.visibility = '';
			}
		} catch (err) { }
	}
	formatJson(p) {
		let outputString = "";
		if (this.popupProperties) {
			for (let i = 0; i < this.popupProperties.length; i++) {
				let key = this.popupProperties[i];
				if (p.hasOwnProperty(key)) {
					if (p[key].includes(",")) {
						outputString += "<span>" + (key + ": " + p[key].replace(",", ", ")) + "<span />" + "<br />";
					} else {
						outputString += "<span>" + (key + ": " + p[key]) + "<span />" + "<br />";
					}
				}
			}
		} else {

			for (var key in p) {
				if (p.hasOwnProperty(key)) {
					if (typeof p[key] === 'string' || p[key] instanceof String) {
						if (p[key].includes("http")) {
							if (p[key].includes(".jpg") || p[key].includes(".png")) {
								outputString += "<img src=" + p[key] + " style='max-width: 100%'> </img>";
							}
						} else {
							if (p[key].includes(",")) {
								outputString += "<span>" + (key + ": " + p[key].replace(",", ", ")) + "</span>" + "</br>";
							} else {
								outputString += "<span>" + (key + ": " + p[key]) + "</span>" + "</br>";
							}
						}
					}
				}
			}
		}
		return outputString;
	}
	getItemsList() {

	}
	getItemsSource() {

	}
	registerListChangedNotifier(notifier) {
		this.listChangedNotifier = notifier;
	}
	registerListChangedFilterNotifier(notifier) {
		this.listChangedFilterNotifier = notifier;
	}
	registerListLoadingOnNotifier(notifier) {
		this.listLoadingNotifier = notifier;
	}
	registerListLoadingOffNotifier(notifier) {
		this.listLoadingDoneNotifier = notifier;
	}
	registerItemAddedNotifier(notifier) {
		this.itemAddedNotifier = notifier;
	}
	registerItemRemovedNotifier(notifier) {
		this.itemRemovedNotifier = notifier;
	}
	setPointOfInterestAndRadius(coords, radius) {
		this.centerPoint = coords;
		this.radius = radius;
	}
	notifySearchSelection(item) {

	}
	requestFieldValues(key, callback) {

	}
	registerVisibilityNotifier(callback) {
		this.visibilityCallbacks.push(callback);
	}
	notifyVisibility(value) {
		for (let i = 0; i < this.visibilityCallbacks.length; i++) {
			this.visibilityCallbacks[i](value);
		}
	}
	setControlMode(mode) {

	}
	notifyChangeFilterData(type, name, values) {

	}
	notifyAreaSelection(area, areaMarkerGroupMaxLevel = this.options.markerGrouping.maxLevel) {

	}
	preRenderCallback() {
		if (this.selectedItem && this.externalRenderObj && this.externalRenderObj.selectedItem) {
			let item = document.getElementById(this.externalRenderObj.selectedItem.id);
			if (item)
				item.style.display = 'none';
			let coordsPoint = this.externalRenderObj.selectedItem.coordinates;
			let screenCoords = this.mapView.transformWgsToScreen(coordsPoint);
			let pos = this.externalRenderObj.getMarkerPos(this.externalRenderObj.options.markerPos, screenCoords, this.externalRenderObj.options.markerWidth, this.externalRenderObj.options.markerHeight);
			this.selectedItem.style.transform = "translate3d(" + pos.dx + "px," + pos.dy + "px,0px)";
		}
	}
	itemSelected(item) {
		if (this.selectedItem) {
			this.selectedItem.remove();
			let item = document.getElementById(this.selectedItemObj.id);
			if (item)
				item.style.display = '';
		}
		this.mapView.registerOnTouchEvent(function (coords) {
			if (this.selectedItem) {
				this.selectedItem.remove();
				let item = document.getElementById(this.selectedItemObj.id);
				if (item)
					item.style.display = '';
				this.selectedItem = undefined;
				if (this.itemSelectionNotifier)
					this.itemSelectionNotifier();
			}
		}.bind(this));

		this.selectedItem = item.cloneNode();
		this.selectedItemObj = this.externalRenderObj.selectedItem;
		this.selectedItem.id = "selectedItem";
		this.selectedItem.className = this.selectedItemClass;
		this.selectedItem.style.pointerEvents = 'none';
		this.selectedItem.style.zIndex = gem.control.zIndex.markerSelected;
		item.style.display = 'none';
		var event = new MouseEvent('mouseout', {
			'view': window,
			'bubbles': true,
			'cancelable': true
		});
		item.dispatchEvent(event);

		let prevBubble = this.selectedItem.getElementsByClassName(this.options.markerBubble.markerBubbleClass);
		if (prevBubble && prevBubble.length)
			prevBubble[0].remove();
		let jsonProps = {};
		if (this.selectedItemObj.info.properties)
			jsonProps = this.selectedItemObj.info.properties;
		else if (this.selectedItemObj.info.parameters) {
			for (const param of this.selectedItemObj.info.parameters.keyvals) {
				jsonProps[param.key] = param.value;
			}
		}

		if (this.options.markerBubble.enableClick) {
			if (this.options.markerBubble.markerBubbleFunction) {
				let selectedBubble = document.createElement('div');
				selectedBubble.id = this.options.markerBubble.markerBubbleClass;
				selectedBubble.className = this.options.markerBubble.markerBubbleClass;
				this.options.markerBubble.markerBubbleFunction(selectedBubble, jsonProps, this.selectedItemObj.coordinates);
				this.selectedItem.appendChild(selectedBubble);
			} else {
				let selectedBubble = this.createMarkerBubble(this.selectedItem, jsonProps);
				if (selectedBubble)
					this.selectedItem.appendChild(selectedBubble);
			}
		}

		// highlight selected item image
		let elHighlightImage = this.selectedItem.appendChild(document.createElement('div'));
		elHighlightImage.className = item.className;
		elHighlightImage.classList.add(this.options.marker.highlightClass);
		item.parentElement.appendChild(this.selectedItem);
		// add item children if there are any
		if (item.firstChild) {
			let itemChildren = item.firstChild.cloneNode(true);
			if (itemChildren)
				elHighlightImage.appendChild(itemChildren);
		}

		if (this.itemSelectionNotifier)
			this.itemSelectionNotifier(this.selectedItemObj);
	}
	registerItemSelectionListener(callback) {
		this.itemSelectionNotifier = callback;
	}

	createMarkerBubble(item, jsonProperties) {
		// validate bubble options
		if (!this.options || !this.options.markerBubble)
			return;
		let optionsBubble = this.options.markerBubble;
		if (!optionsBubble.title.length && !optionsBubble.image.length)
			return this.createDefaultBubble(item, jsonProperties);
		if (!optionsBubble.title[0].length && !optionsBubble.image[0].length)
			return this.createDefaultBubble(item, jsonProperties);

		let bubble = document.createElement('div');
		bubble.style.height = 'auto';
		bubble.style.width = this.options.markerBubble.width + "px";
		bubble.id = this.options.markerBubble.markerBubbleClass;
		bubble.className = this.options.markerBubble.markerBubbleClass;

		// image
		let imgUrl = jsonProperties ? jsonProperties[this.options.markerBubble.image] : undefined;
		if (imgUrl) {
			let elImgContainer = document.createElement('div');
			elImgContainer.className = 'gem-popup-img-container';
			let elImg = document.createElement('img');
			elImg.className = 'gem-popup-img';
			elImg.onload = () => {
				elImgContainer.appendChild(elImg);
				bubble.style.height = this.options.markerBubble.height + "px";
			};
			elImg.onerror = () => {
				bubble.style.height = 'auto';
			};
			elImg.src = imgUrl;
			bubble.appendChild(elImgContainer);
		} else {
			bubble.style.height = 'auto';
		}

		// text container
		let popUpTextContainer = document.createElement('div');
		popUpTextContainer.className = 'gem-marker-bubble-text';
		// title
		let popUpTitle = document.createElement('div');
		popUpTitle.className = 'gem-marker-bubble-title';
		popUpTitle.textContent = this.getPropertyString(jsonProperties, this.options.markerBubble.title, ',');
		if (!popUpTitle.textContent.length)
			return undefined;
		popUpTextContainer.appendChild(popUpTitle);
		bubble.appendChild(popUpTextContainer);

		this.adjustBubbleLocation(item, bubble, this.options.markerBubble.height, this.options.markerBubble.width, imgUrl ? true : false);
		return bubble;
	}

	createDefaultBubble(item, jsonObject) {
		let bubble = document.createElement('div');
		bubble.id = this.options.markerBubble.markerBubbleClass;
		bubble.classList = this.options.markerBubble.markerBubbleClass;
		bubble.style.width = this.options.markerBubble.width + "px";
		bubble.style.height = this.options.markerBubble.height + "px";

		let popupText = bubble.appendChild(document.createElement('div'));
		popupText.className = 'gem-marker-bubble-text';
		let popupDetails = popupText.appendChild(document.createElement('div'));
		popupDetails.className = 'gem-marker-bubble-details';
		popupDetails.innerHTML = this.formatJson(jsonObject);

		let imgUrl = jsonObject[this.imageProperty];
		if (imgUrl) {
			let elImgContainer = document.createElement('div');
			elImgContainer.className = 'gem-popup-img-container';
			let elImg = document.createElement('img');
			elImg.className = 'gem-popup-img';
			elImg.src = imgUrl;
			elImgContainer.appendChild(elImg);
			bubble.appendChild(elImgContainer);
		} else {
			bubble.style.height = 'auto';
		}
		this.adjustBubbleLocation(item, bubble, this.options.markerBubble.height, this.options.markerBubble.width, imgUrl ? true : false);
		return bubble;
	}

	adjustBubbleLocation(item, bubble, bubbleHeight, bubbleWidth, hasImage) {
		let mapCanvas = document.getElementById(gem.core.App.initOptions.container);
		let itemTransform = item.style.transform;
		let arrTransform = itemTransform.match(/\d+([\.,]\d+)?/g).map(Number);
		let itemX;
		let itemY;
		if (itemTransform.indexOf('translate3d') > -1) {
			itemX = arrTransform[1];
			itemY = arrTransform[2];
		} else if (itemTransform.indexOf('translate') > -1) {
			itemX = arrTransform[0];
			itemY = arrTransform[1];
		}
		if (!itemX || !itemY) {
			bubble.classList.add('gem-marker-bubble--top-center');
			bubble.style.marginLeft = "-" + (this.options.markerBubble.width / 2) + "px";
			return bubble;
		}
		if (itemY < bubbleHeight) {
			if (itemX < bubbleWidth / 2)
				bubble.classList.add('gem-marker-bubble--bottom-right');
			else if (itemX > mapCanvas.offsetWidth - bubbleWidth / 2)
				bubble.classList.add('gem-marker-bubble--bottom-left');
			else {
				bubble.classList.add('gem-marker-bubble--bottom-center');
				bubble.style.marginLeft = "-" + (this.options.markerBubble.width / 2) + "px";
			}
		} else if (itemX < bubbleWidth / 2) {
			if (itemY > mapCanvas.offsetHeight - bubbleHeight / 2)
				bubble.classList.add('gem-marker-bubble--top-right');
			else {
				if (hasImage)
					bubble.classList.add('gem-marker-bubble--right-center');
				else
					bubble.classList.add('gem-marker-bubble-no-img--right-center');
			}
		} else if (itemX > mapCanvas.offsetWidth - bubbleWidth / 2) {
			if (itemY > mapCanvas.offsetHeight - bubbleHeight / 2)
				bubble.classList.add('gem-marker-bubble--top-left');
			else {
				if (hasImage)
					bubble.classList.add('gem-marker-bubble--left-center');
				else
					bubble.classList.add('gem-marker-bubble-no-img--left-center');
			}
		} else {
			bubble.classList.add('gem-marker-bubble--top-center');
			bubble.style.marginLeft = "-" + (this.options.markerBubble.width / 2) + "px";
		}
	}

	getPropertyString(markerProps, arrProperties, separator = ', ') {
		if (!markerProps)
			return;
		let stringProperty = '';
		for (let i = 0; i < arrProperties.length; i++) {
			let propValue = markerProps[arrProperties[i]];
			if (propValue && propValue.length) {
				stringProperty += propValue;
				if (stringProperty.length > 0 && i < arrProperties.length - 1)
					stringProperty += separator;
			}
		}
		return stringProperty;
	}

	checkFiltersOnJsonProperties(jsonProperties, filtersMap) {
		let filtersCount = filtersMap.size;
		for (const [name, filters] of filtersMap.entries()) {
			if (filters) {
				for (let i = 0; i < filters.length; i++) {
					if (!filters[i].key.length) {
						filtersCount--;
						continue;
					}
					let prop = jsonProperties[filters[i].key];
					if (prop) {
						if (!filters[i].value.length)
							filtersCount--;
						else if (prop == filters[i].value)
							filtersCount--;
					}
					if (filtersCount <= 0)
						return true;
				}
				if (!filters.length)
					filtersCount--;
			}
		}
		return filtersCount <= 0;
	}

	iconFilter(item) {
		try {
			let jsonBuffer = JSON.parse(item.getInfo());
			let jsonProperties = {};
			if (jsonBuffer.properties)
				jsonProperties = jsonBuffer.properties;
			else if (jsonBuffer.parameters) {
				for (const param of jsonBuffer.parameters.keyvals) {
					jsonProperties[param.key] = param.value;
				}
			}
			for (let i = 0; i < this.iconFilter.length; i++) {
				let propValue = jsonProperties[this.iconFilter[i].key];
				if (propValue && propValue.length) {
					if (propValue.includes(this.iconFilter[i].value))
						return this.iconFilter[i].iconClass;
				}
			}
		} catch (e) {

		}
		return this.options.marker.cssClass;
	}
}
/** Control for displaying data from a Studio defined data source
 * @class gem.control.StudioAddedDataControl
 * @memberof gem.control
 * 
 * @param {string} layerMarkersId - studio defined data source id
 * @param {string} iconLocation - path/url to marker image
 * @param {object} dataOptions - options for displaying data source items
 * 	 @param {object} dataOptions.marker - options for markers display on map
 * 	   @param {string} dataOptions.marker.cssClass - specify custom marker style rules
 * 	   @param {string} dataOptions.marker.highlightClass - specify selected marker custom highlight style rules
 * 	   @param {number} [dataOptions.marker.width = 20]
 *	   @param {number} [dataOptions.marker.height = 20]
 *	   @param {number} [dataOptions.marker.hoverWidth = 25]
 *	   @param {number} [dataOptions.marker.hoverHeight = 25]
 *     @param {string} [dataOptions.marker.markerPos = 'center'] - where to place the marker relative to the item coordinate
 *     @param {callbackMarker} dataOptions.marker.markerFunction - fully customize marker appearance
 *   @param {object} dataOptions.markerBubble - options for styling the marker bubble on click/hover
 *     @param {string[]} dataOptions.markerBubble.title - data source properties to use for marker bubble title text
 *     @param {string} dataOptions.markerBubble.image - data source image url property to use for marker bubble image
 *     @param {number} [dataOptions.markerBubble.width = 240] - bubble width in px
 * 	   @param {number} [dataOptions.markerBubble.height = 200] - bubble height in px
 *     @param {bool} [dataOptions.markerBubble.enableHover = true] - disable marker bubble interaction on hover
 *	   @param {bool} [dataOptions.markerBubble.enableClick = true] - disable marker bubble interaction on click
 * 	   @param {string} dataOptions.markerBubble.markerBubbleClass - custom css class for marker bubble
 *     @param {callbackMarkerBubble} dataOptions.markerBubble.markerBubbleFunction - fully customize marker bubble
 *   @param {object} dataOptions.markerGrouping - marker grouping options
 *	   @param {object} [dataOptions.markerGrouping.style = gem.control.MarkersGroupStyleType.default] - style to apply to marker groups
 *     @param {callbackMarkerGroup} [dataOptions.markerGrouping.markerGroupFunction] - fully customize marker groups style
 * @param {object[]} [iconFilter] - filters for applying different icons to markers based on their properties
 *    @param {string} iconFilter[].key - data source item property key
 *    @param {string} iconFilter[].value - data source item property value
 *    @param {string} iconFilter[].iconClass - css class to use for this filter
 */
gem.control.StudioAddedDataControl = class StudioAddedDataControl extends gem.control.AddedDataControl {
	constructor(layerMarkersId, iconLocation, dataOptions, iconFilter) {
		super(gem.control.TConntrolExecuteType.STYLE, gem.control.TConntrolItemUpdateMode.INDIVIDUAL, dataOptions);
		this.layerMarkersId = layerMarkersId;
		this.iconLocation = iconLocation;
		this.itemsMap = new Map();
		if (iconFilter) {
			this.iconFilter = iconFilter;
			this.iconLocation = super.iconFilter.bind(this);
		}

		this.mouseOver = super.mouseOver;
		this.mouseOut = super.mouseOut;
		this.mouseClick = function (item, jsonObject, position) {
			return false;
		};
	}
	initControl(parentDiv, mapView) {
		this.mapView = mapView;
		// The markers on the map are shown using the icon.svg file which is
		// located in the same directory as this JavaScript code

		this.externalRenderObj = new gem.d3Scene.ExternalRendererMarkers(
			this.iconLocation, "myCollection", mapView, parentDiv, this.options.marker.cssClass, this.rendererOptions);
		this.externalRenderObj.registerMouseOverCallback(this.mouseOver.bind(this));
		this.externalRenderObj.registerMouseOutCallback(this.mouseOut.bind(this));
		this.externalRenderObj.registerMouseClickCallback(this.mouseClick.bind(this));
		this.externalRenderObj.registerItemAddedCallback(function (itemElementHtmlId, item, itemId, isGroup) {
			return this.addItemToList(itemElementHtmlId, item, itemId, isGroup);
		}.bind(this));
		this.externalRenderObj.registerItemRemovedCallback(function (itemElementHtmlId, item, itemId) {
			this.removeItemFromList(itemElementHtmlId, item, itemId);
		}.bind(this));
		if (this.selectedItemClass)
			this.externalRenderObj.registerItemSelectedCallback(this.itemSelected.bind(this));
		mapView.enableOverlayItemWithExternalRendering(this.layerMarkersId, this.externalRenderObj);
		let cameraMovedCallback = function () {
			this.mapView.registerForNextRenderFinished(this.filterData.bind(this));
		}
		this.canShowData = true;
		this.mCameraCallback = cameraMovedCallback.bind(this);
		if (this.controlMode && this.controlMode != 'automatic') {
			this.notifyVisibility('hidden');
			this.canShowData = false;
			return;
		}
	}
	addItemToList(itemElementHtmlId, item, itemId, isGroup) {
		let it = this.itemsMap.get(itemId);
		if (item)
			item.cacheData();
		let visible = true;
		if (!this.checkFilters(item)) {
			let pt = document.getElementById(itemElementHtmlId + itemId);
			if (pt)
				pt.style.visibility = 'hidden';
			visible = false;
		} else {
			let pt = document.getElementById(itemElementHtmlId + itemId);
			if (pt)
				pt.style.visibility = 'visible';
			visible = true;
		}
		if (it === undefined) {
			this.itemsMap.set(itemId, {
				marker: item,
				isGroup: isGroup,
				visible: visible,
				itemElementHtmlId: itemElementHtmlId
			});
		} else {
			it.visible = visible;
		}
		if (!this.hasSearchSelection)
			this.mapView.registerForNextRenderFinished(this.filterData.bind(this));
	}
	checkFilters(item) {
		if (!this.canShowData)
			return false;
		if (this.centerPoint && this.radius) {
			let distance = gem.core.App.getDistanceBetween2Coordinates(item.getCoordinates(), this.centerPoint);
			if (distance > this.radius) {
				return false;
			}
		}

		if (this.filtersMap && this.filtersMap.size) {
			try {
				let jsonBuffer = JSON.parse(item.getInfo());
				if (jsonBuffer.parameters && jsonBuffer.parameters.keyvals) {
					let jsonProperties = {};
					for (const param of jsonBuffer.parameters.keyvals) {
						jsonProperties[param.key] = param.value;
					}
					return super.checkFiltersOnJsonProperties(jsonProperties, this.filtersMap);
				}
			} catch (e) {
				return false;
			}
		}
		return true;
	}
	notifySearchSelection(item) {
		this.hasSearchSelection = true;
		if (item === undefined) {
			this.notifyVisibility('hidden');
			this.canShowData = false;
			this.filterData();
			this.itemsMap.clear();
			if (this.selectedItem) {
				this.selectedItem.remove();
				this.selectedItem = undefined;
			}
			return;
		}
		if (this.selectedItem) {
			this.selectedItem.remove();
			this.selectedItem = undefined;
		}
		this.itemsMap.clear();
		this.notifyVisibility('visible');
		this.canShowData = true;
		if (this.radius === undefined)
			this.radius = 10000;
		this.setPointOfInterestAndRadius(item.getCoordinates(), this.radius);
	}
	filterData() {
		let filteredMap = new Map();

		function logMapElements(value, key, map) {
			if (!this.checkFilters(value.marker)) {
				let pt = document.getElementById(value.itemElementHtmlId + key);
				if (pt)
					pt.style.visibility = 'hidden';
				value.visible = false;
			} else {
				let pt = document.getElementById(value.itemElementHtmlId + key);
				if (pt)
					pt.style.visibility = 'visible';
				value.visible = true;
			}
			if (value.visible)
				filteredMap.set(key, value);
		}
		this.itemsMap.forEach(logMapElements.bind(this));
		if (this.listChangedNotifier)
			this.listChangedNotifier(filteredMap, this.externalRenderObj.name, this.mapView);
	}
	removeItemFromList(itemElementHtmlId, item, itemId) {
		if (!this.hasSearchSelection) {
			this.itemsMap.delete(itemId);
			this.mapView.registerForNextRenderFinished(this.filterData.bind(this));
		}
	}
	/**
	 * 
	 * @param {gem.core.Coordinates} coords 
	 * @param {number} radius - radius in meters
	 */
	setPointOfInterestAndRadius(coords, radius) {
		this.centerPoint = coords;
		this.radius = radius;
		this.geographicList = new gem.core.RectangleGeographicAreaList();
		let geographicArea = new Module.RectangleGeographicArea();
		let offset = (this.radius / 6371000) * 57.295779513;
		let topLeft = {
			latitude: this.centerPoint.latitude + offset,
			longitude: this.centerPoint.longitude - offset,
			altitude: this.centerPoint.altitude,
			bearing: this.centerPoint.bearing
		};
		let bottomRight = {
			latitude: this.centerPoint.latitude - offset,
			longitude: this.centerPoint.longitude + offset,
			altitude: this.centerPoint.altitude,
			bearing: this.centerPoint.bearing
		};
		geographicArea.setTopLeft(topLeft);
		geographicArea.setBottomRight(bottomRight);
		this.geographicList.push_back(geographicArea);
		this.mapView.centerOnArea(topLeft, bottomRight, 0);
		this.filterData();
	}
	cameraChangedCallback() {
		if (this.mCameraCallback) {
			this.mCameraCallback();
		}
	}
	notifyChangeFilterData(type, name, values) {
		switch (type) {
			case 'distance': {
				this.radius = parseFloat(values) * 1000;
				if (this.centerPoint)
					this.setPointOfInterestAndRadius(this.centerPoint, this.radius);
				break;
			}
			case 'category': {
				if (!this.filtersMap)
					this.filtersMap = new Map();
				this.filtersMap.set(name, values);
				this.filterData();
				break;
			}
		}
	}
	setControlMode(mode) {
		this.controlMode = mode;
	}
}
/**
 * @class gem.core.AppScreen
 *@memberof gem.core
 */
gem.core.AppScreen = class AppScreen {
	constructor(container) {
		this.controlsStyleArray = new Array();
		this.controlsOnConnectedArray = new Array();
		this.connectionChangedArray = new Array();
		this.container = container;
	}
	registerOnConnectionChangedCallback(cb) {
		this.connectionChangedArray.push(cb);
	}
	initFinished() {
		gem.core.App.registerConnectionStatusChanged(function (online) {
			// Search requires a network connection, so check if online
			if (online) {
				let i;
				for (i = 0; i < this.controlsOnConnectedArray.length; i++) {
					this.controlsOnConnectedArray[i].initControl(this.container, this.defaultMap);
				}
				for (i = 0; i < this.connectionChangedArray.length; i++) {
					this.connectionChangedArray[i](online);
				}
			}
		}.bind(this));
		this.defaultScreen = gem.core.App.getDefaultScreen();
		this.defaultMap = this.defaultScreen.getDefaultMapView();
		this.defaultMap.registerStyleUpdateFinishedCallback(function () {
			let i;
			for (i = 0; i < this.controlsStyleArray.length; i++) {
				this.controlsStyleArray[i].initControl(this.container, this.defaultMap);
			}
		}.bind(this));
		this.defaultMap.registerCameraChangeStateCallback(
			function (camState) {
				if (camState != 0)
					return;
				let i = 0;
				for (i = 0; i < this.controlsOnConnectedArray.length; i++) {
					this.controlsOnConnectedArray[i].cameraChangedCallback();
				}
				for (i = 0; i < this.controlsStyleArray.length; i++) {
					this.controlsStyleArray[i].cameraChangedCallback();
				}
			}.bind(this)
		)
		Module.registerPostRenderCallback(
			function () {
				let i = 0;
				for (i = 0; i < this.controlsOnConnectedArray.length; i++) {
					this.controlsOnConnectedArray[i].preRenderCallback();
				}
				for (i = 0; i < this.controlsStyleArray.length; i++) {
					this.controlsStyleArray[i].preRenderCallback();
				}
			}.bind(this)
		);
	}
	/**
	 * 
	 * @param {gem.control.BaseControl} pControl 
	 */
	addControl(pControl) {
		if (this.defaultMap !== undefined) {
			pControl.initControl(this.container, this.defaultMap);
		} else {
			switch (pControl.getType()) {
				case gem.control.TConntrolExecuteType.STYLE:
					this.controlsStyleArray.push(pControl);
					break;
				case gem.control.TConntrolExecuteType.ONCONNECTED:
					this.controlsOnConnectedArray.push(pControl);
					break;
			}
		}

	}
}
/** 
 * Control for displaying data from a GeoJSON source
 * @class gem.control.GeoJsonAddedDataControl
 * @memberof gem.control
 * 
 * @param {string} geoJsonFileOrObject - GeoJSON data file path/url or string object
 * @param {string} iconLocation - path/url to marker image/icon
 * @param {object[]} [iconFilter] - filters for applying different icons to markers based on their properties
 *    @param {string} iconFilter[].key - data source item property key 
 *    @param {string} iconFilter[].value - data source item property value
 *    @param {string} iconFilter[].iconClass - css class to use for this filter   
 * @param {object} [dataOptions] - options for displaying data source items
 * 	 @param {object} dataOptions.marker - options for markers display on map
 * 	   @param {string} [dataOptions.marker.cssClass] - specify custom marker style rules
 * 	   @param {string} [dataOptions.marker.highlightClass] - specify selected marker custom highlight style rules
 * 	   @param {number} [dataOptions.marker.width = 20]
 *	   @param {number} [dataOptions.marker.height = 20]
 *	   @param {number} [dataOptions.marker.hoverWidth = 25]
 *	   @param {number} [dataOptions.marker.hoverHeight = 25]
 *     @param {string} [dataOptions.marker.markerPos = 'center'] - where to place the marker relative to the item coordinate
 *     @param {callbackMarker} dataOptions.marker.markerFunction - fully customize marker appearance
 *   @param {object} dataOptions.markerBubble - options for styling the marker bubble on click/hover
 *     @param {string[]} dataOptions.markerBubble.title - data source properties to use for marker bubble title text
 *     @param {string} dataOptions.markerBubble.image - data source image url property to use for marker bubble image
 *     @param {number} [dataOptions.markerBubble.width = 240] - bubble width in px
 * 	   @param {number} [dataOptions.markerBubble.height = 200] - bubble height in px
 *     @param {bool} [dataOptions.markerBubble.enableHover = true] - disable marker bubble interaction on hover
 *	   @param {bool} [dataOptions.markerBubble.enableClick = true] - disable marker bubble interaction on click
 * 	   @param {string} [dataOptions.markerBubble.markerBubbleClass] - custom css class for marker bubble
 *     @param {callbackMarkerBubble} [dataOptions.markerBubble.markerBubbleFunction] - fully customize marker bubble
 *   @param {object} [dataOptions.markerGrouping] - marker grouping options
 *     @param {number} [dataOptions.markerGrouping.maxLevel = 13] - maximum map level to apply grouping ( max allowed 15 )
 *	   @param {object} [dataOptions.markerGrouping.style = gem.control.MarkersGroupStyleType.default] - style to apply to marker groups
 *     @param {callbackMarkerGroup} [dataOptions.markerGrouping.markerGroupFunction] - fully customize marker groups style
 *   @param {bool} [dataOptions.disableZoomToData = false ] - disables zooming to the data source area at start-up
 */
gem.control.GeoJsonAddedDataControl = class GeoJsonAddedDataControl extends gem.control.AddedDataControl {
	constructor(geoJsonFileOrObject, iconLocation, iconFilter, dataOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED, gem.control.TConntrolItemUpdateMode.LIST, dataOptions);
		if (typeof geoJsonFileOrObject === 'string' && geoJsonFileOrObject.substring(geoJsonFileOrObject.length - 4).toUpperCase() === 'JSON')
			this.geojsonFile = geoJsonFileOrObject;
		else
			this.geoJsonString = geoJsonFileOrObject;
		this.iconLocation = iconLocation;
		if (iconFilter) {
			this.iconFilter = iconFilter;
			this.iconLocation = super.iconFilter.bind(this);
		}
		this.mouseOver = super.mouseOver;
		this.mouseOut = super.mouseOut;
		this.mouseClick = super.mouseClick;
	}

	doParseData(parentDiv, mapView) {
		// The markers on the map are shown using the icon.svg file which is
		// located in the same directory as this JavaScript code
		mapView.addGeoJsonAsCustomMarkers(JSON.stringify(this.geoJsonString),
			this.controlName, this.tvectorSources, this.externalRenderObj, this.options.markerGrouping.maxLevel);
		if (!this.options.disableZoomToData) {
			let sourceArea = this.tvectorSources.get(0).getArea();
			if (!sourceArea.isEmpty) {
				// adjust center on rectangle
				const viewBuffer = 50;
				let viewRectangle = mapView.getViewport();
				viewRectangle.x += viewBuffer;
				viewRectangle.y += viewBuffer;
				viewRectangle.width -= 2 * viewBuffer;
				viewRectangle.height -= 2 * viewBuffer;
				mapView.centerOnArea(sourceArea.leftTop, sourceArea.rightBottom, 0, viewRectangle);
			}
			else
			{
				// may be the case of a single point
				if(this.tvectorSources.size()==1)
				{
					let markerColl = this.tvectorSources.get(0);
					if(markerColl.size() == 1)
					{
						let marker = markerColl.getMarkerAt(0);
						let coords = marker.getCoordinatesPartAt(0);
						let coordsPoint = coords.get(0);
						mapView.centerOnCoordinates(coordsPoint);
					}

				}
			}
		}
		if (this.listChangedNotifier)
			this.listChangedNotifier(this.externalRenderObj.name, mapView);
	}
	initControl(parentDiv, mapView) {
		this.itemsMap = new Map();

		this.mapView = mapView;
		this.controlName = "geojsonAddedData";
		this.tvectorSources = new gem.d3Scene.MarkerCollectionRefList();
		this.externalRenderObj = new gem.d3Scene.ExternalRenderer(
			this.iconLocation, this.controlName, mapView, parentDiv, this.options.marker.cssClass, this.rendererOptions);
		this.externalRenderObj.registerMouseOverCallback(this.mouseOver.bind(this));
		this.externalRenderObj.registerMouseOutCallback(this.mouseOut.bind(this));
		this.externalRenderObj.registerMouseClickCallback(this.mouseClick.bind(this));
		if (this.selectedItemClass)
			this.externalRenderObj.registerItemSelectedCallback(this.itemSelected.bind(this));
		this.externalRenderObj.registerItemAddedCallback(function (itemElementHtmlId, item, itemId, isGroup) {
			if (this.itemAddedNotifier) this.itemAddedNotifier(itemElementHtmlId, item, itemId, isGroup);
		}.bind(this));
		this.externalRenderObj.registerItemRemovedCallback(function (itemElementHtmlId, item, itemId) {
			if (this.itemRemovedNotifier) this.itemRemovedNotifier(itemElementHtmlId, item, itemId);
		}.bind(this));

		let cameraMovedCallback = function () {
			this.mapView.registerForNextRenderFinished(this.filterData.bind(this));
		}
		this.mCameraCallback = cameraMovedCallback.bind(this);

		if (this.geojsonFile) {
			fetch(this.geojsonFile)
				.then(response => response.text())
				.then(textData => JSON.parse(textData))
				.then(jsonData => {
					this.geoJsonString = jsonData;
					this.doParseData(parentDiv, mapView);
				});
		} else
			this.doParseData(parentDiv, mapView);
	}
	cameraChangedCallback() {
		if (this.mCameraCallback)
			this.mCameraCallback();
	}
	getItemsList() {
		return this.tvectorSources.get(0);
	}
	getItemsSource() {
		return this.tvectorSources.get(0);
	}

	itemAddedNotifier(itemElementHtmlId, item, itemId, isGroup) {
		let it = this.itemsMap.get(itemId);
		let visible = true;
		let pt = document.getElementById(itemElementHtmlId + itemId);
		if (!this.checkFilters(item)) {
			if (pt)
				pt.style.visibility = 'hidden';
			visible = false;
		} else {
			if (pt)
				pt.style.visibility = 'visible';
			visible = true;
		}
		if (it === undefined) {
			this.itemsMap.set(itemId, {
				marker: item,
				isGroup: isGroup,
				visible: visible,
				itemElementHtmlId: itemElementHtmlId
			});
		} else {
			it.visible = visible;
			it.marker = item;
			it.isGroup = isGroup;
		}

		// get group children and filter
		if (visible && isGroup) {
			let source = this.getItemsList();
			if (source) {
				let children = source.getGroupItemsVector(String(itemId));
				let childrenSize = children.size();
				for (let i = 0; i < childrenSize; i++) {
					let child = children.get(i);
					//let id = child.getId();
					//let childMarker = new gem.d3Scene.MarkerRef(child);
					//if (!this.checkFilters(childMarker))
					//	childrenSize--;
					//console.log(child);
				}
				//console.log("children size", childrenSize);
			}
		}

		if (!this.hasSearchSelection)
			this.mapView.registerForNextRenderFinished(this.filterData.bind(this));
	}

	itemRemovedNotifier(itemElementHtmlId, item, itemId) {
		if (!this.hasSearchSelection) {
			this.itemsMap.delete(itemId);
			this.mapView.registerForNextRenderFinished(this.filterData.bind(this));
		}
	}

	notifyChangeFilterData(type, name, values) {
		switch (type) {
			case 'distance':
				this.radius = parseFloat(values) * 1000;
				if (this.centerPoint)
					this.setPointOfInterestAndRadius(this.centerPoint, this.radius);
				break;
			case 'category':
				if (!this.filtersMap)
					this.filtersMap = new Map();
				this.filtersMap.set(name, values);
				this.filterData();
				break;
		}
	}

	setPointOfInterestAndRadius(coords, radius) {
		this.centerPoint = coords;
		this.radius = radius;

		this.geographicList = new gem.core.RectangleGeographicAreaList();
		let geographicArea = new Module.RectangleGeographicArea();
		let offset = (this.radius / 6371000) * 57.295779513;
		let topLeft = {
			latitude: this.centerPoint.latitude + offset,
			longitude: this.centerPoint.longitude - offset,
			altitude: this.centerPoint.altitude,
			bearing: this.centerPoint.bearing
		};
		let bottomRight = {
			latitude: this.centerPoint.latitude - offset,
			longitude: this.centerPoint.longitude + offset,
			altitude: this.centerPoint.altitude,
			bearing: this.centerPoint.bearing
		};
		geographicArea.setTopLeft(topLeft);
		geographicArea.setBottomRight(bottomRight);
		this.geographicList.push_back(geographicArea);
		this.mapView.centerOnArea(topLeft, bottomRight, 0);
		this.filterData();
	}

	checkFilters(item) {
		if (this.centerPoint && this.radius) {
			let coords;
			if (item)
				coords = item.getCoordinatesAt(0).get(0);
			let distance = gem.core.App.getDistanceBetween2Coordinates(coords, this.centerPoint);
			if (distance > this.radius) {
				return false;
			}
		}

		if (this.filtersMap && this.filtersMap.size) {
			try {
				let jsonBuffer = JSON.parse(item.getInfo());
				let jsonProperties = jsonBuffer.properties;
				return super.checkFiltersOnJsonProperties(jsonProperties, this.filtersMap);
			} catch (e) {
				return false;
			}
		}
		return true;
	}

	filterData() {
		if (!this.filtersMap || !this.filtersMap.size)
			return;
		let filteredMap = new Map();

		function logMapElements(value, key, map) {
			if (!this.checkFilters(value.marker)) {
				let pt = document.getElementById(value.itemElementHtmlId + key);
				if (pt)
					pt.style.visibility = 'hidden';
				value.visible = false;
			} else {
				let pt = document.getElementById(value.itemElementHtmlId + key);
				if (pt)
					pt.style.visibility = 'visible';
				value.visible = true;
			}
			if (value.visible)
				filteredMap.set(key, value);
		}
		this.itemsMap.forEach(logMapElements.bind(this));
		if (this.listChangedFilterNotifier)
			this.listChangedFilterNotifier(filteredMap, this.externalRenderObj.name, this.mapView);
	}
}

/** Control for displaying data from SQL server data source
 * @class gem.control.QueryAddedDataControl
 * @memberof gem.control
 * 
 *  @param {object} initQueryAddedDataOptions - options for initializing the SQL query source
 *  @param {string[]} initQueryAddedDataOptions.languages - list of language iso codes
 *  @param {number} initQueryAddedDataOptions.storeId - available options store id 1 or 2
 *  @param {string} iconLocation - path/url to marker image/icon
 *  @param {object} dataOptions - options for displaying data source items
 * 	 @param {object} [dataOptions.marker] - options for markers display on map
 * 	   @param {string} [dataOptions.marker.cssClass] - specify custom marker style rules
 * 	   @param {string} [dataOptions.marker.highlightClass] - specify selected marker custom highlight style rules
 * 	   @param {number} [dataOptions.marker.width = 20]
 *	   @param {number} [dataOptions.marker.height = 20]
 *	   @param {number} [dataOptions.marker.hoverWidth = 25]
 *	   @param {number} [dataOptions.marker.hoverHeight = 25]
 *     @param {string} [dataOptions.marker.markerPos = 'center'] - where to place the marker relative to the item coordinate
 *     @param {callbackMarker} [dataOptions.marker.markerFunction] - fully customize marker appearance
 *   @param {object} dataOptions.markerBubble - options for styling the marker bubble on click/hover
 *     @param {string[]} dataOptions.markerBubble.title - data source properties to use for marker bubble title text
 *     @param {string} dataOptions.markerBubble.image - data source image url property to use for marker bubble image
 *     @param {number} [dataOptions.markerBubble.width = 240] - bubble width in px
 * 	   @param {number} [dataOptions.markerBubble.height = 200] - bubble height in px
 *     @param {bool} [dataOptions.markerBubble.enableHover = true] - disable marker bubble interaction on hover
 *	   @param {bool} [dataOptions.markerBubble.enableClick = true] - disable marker bubble interaction on click
 * 	   @param {string} dataOptions.markerBubble.markerBubbleClass - custom css class for marker bubble
 *     @param {callbackMarkerBubble} dataOptions.markerBubble.markerBubbleFunction - fully customize marker bubble
 *   @param {object} dataOptions.markerGrouping - marker grouping options
 *     @param {number} dataOptions.markerGrouping.maxLevel - maximum map level to apply grouping ( max allowed 15 )
 *	   @param {object} [dataOptions.markerGrouping.style = gem.control.MarkersGroupStyleType.default] - style to apply to marker groups
 *     @param {callbackMarkerGroup} [dataOptions.markerGrouping.markerGroupFunction] - fully customize marker groups style
 */
gem.control.QueryAddedDataControl = class QueryAddedDataControl extends gem.control.AddedDataControl {
	constructor(initQueryAddedDataOptions, iconLocation, dataOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED, gem.control.TConntrolItemUpdateMode.LIST, dataOptions);
		this.iconLocation = iconLocation;
		if (initQueryAddedDataOptions.languages)
			this.languageList = initQueryAddedDataOptions.languages;
		else
			this.languageList = ["en"];
		if (initQueryAddedDataOptions.storeId) {
			this.storeId = initQueryAddedDataOptions.storeId;
		}
		if (initQueryAddedDataOptions.center) {
			this.centerPoint = initQueryAddedDataOptions.center;
		}
		if (initQueryAddedDataOptions.radius) {
			this.radius = initQueryAddedDataOptions.radius;
		}
		this.mouseOver = super.mouseOver;
		this.mouseOut = super.mouseOut;
		this.mouseClick = super.mouseClick;
	}
	cameraChangedCallback() {
		if (this.mCameraCallback) {
			this.mCameraCallback();
		}
	}
	setControlMode(mode) {
		this.controlMode = mode;
	}
	initControl(parentDiv, mapView) {
		this.mapView = mapView;
		// View of the map
		this.resultStoreList = new gem.core.StoreLocationList();
		// The query result is shown on the map using the icon.svg
		// file which is located in the same directory as this JavaScript code
		this.externalRenderObj = new gem.d3Scene.ExternalRenderer(
			this.iconLocation, "myCollection", mapView, parentDiv, this.options.marker.cssClass, this.rendererOptions);
		this.externalRenderObj.registerMouseOutCallback(this.mouseOut.bind(this));
		this.externalRenderObj.registerMouseOverCallback(this.mouseOver.bind(this));
		this.externalRenderObj.registerMouseClickCallback(this.mouseClick.bind(this));
		if (this.selectedItemClass)
			this.externalRenderObj.registerItemSelectedCallback(this.itemSelected.bind(this));

		//	this.externalRenderObj.registerItemAddedCallback(function (itemElementHtmlId, item) {
		//		if (this.itemAddedNotifier) this.itemAddedNotifier(itemElementHtmlId, item);
		//	}.bind(this));
		//	this.externalRenderObj.registerItemRemovedCallback(function (itemElementHtmlId, item, itemId) {
		//		if (this.itemRemovedNotifier) this.itemRemovedNotifier(itemElementHtmlId, item, itemId);
		//	}.bind(this));
		// This function is called when the query results (more store locations) arrive
		this.callbackquery = function (reason) {
			if (this.tvectorSources) {
				if (this.tvectorSources.size() > 0) {
					if (this.externalRenderObj) {
						this.externalRenderObj.sourceIsPending = true;
						this.externalRenderObj.clear();
						this.externalRenderObj.destroyHasBeenCalled = undefined;
					}
					this.mapView.removeVectorSource(this.tvectorSources.get(0));
				}
				this.tvectorSources.delete();
				this.tvectorSources = new gem.d3Scene.MarkerCollectionRefList();
			} else
				this.tvectorSources = new gem.d3Scene.MarkerCollectionRefList();
			// Add store markers to the map
			mapView.addStoreListAsCustomMarkers(this.resultStoreList,
				"storeLocator", this.tvectorSources, this.externalRenderObj, this.storeId, this.options.markerGrouping.maxLevel);
			if (this.listChangedNotifier) {
				this.listChangedNotifier(this.externalRenderObj.name, mapView, this.storeId);
			}

		}.bind(this);
		this.externalQueryListener = new gem.d3Scene.ExternalQueryListener(this.callbackquery);
		this.listLang = new gem.core.GemStringList();
		// This function is called if the camera moved or changed orientation.
		// Search the store locator again, in case more stores have appeared in view

		let i;
		for (i = 0; i < this.languageList.length; i++) {
			this.listLang.push_back(this.languageList[i]);
		}
		if (this.centerPoint && this.radius) {
			this.geographicList = new gem.core.RectangleGeographicAreaList();
			let geographicArea = new Module.RectangleGeographicArea();
			let offset = (this.radius / 6371) * 57.295779513;
			let topLeft = {
				latitude: this.centerPoint.latitude + offset,
				longitude: this.centerPoint.longitude - offset,
				altitude: this.centerPoint.altitude,
				bearing: this.centerPoint.bearing
			};
			let bottomRight = {
				latitude: this.centerPoint.latitude - offset,
				longitude: this.centerPoint.longitude + offset,
				altitude: this.centerPoint.altitude,
				bearing: this.centerPoint.bearing
			};
			geographicArea.setTopLeft(topLeft);
			geographicArea.setBottomRight(bottomRight);
			this.geographicList.push_back(geographicArea);
			mapView.centerOnArea(topLeft, bottomRight, 0);
			mapView.queryStoreLocator(this.externalQueryListener, this.resultStoreList, this.storeId, false, this.listLang, this.geographicList);
		} else {
			if (this.controlMode && this.controlMode != 'automatic') {
				this.mCameraCallback = undefined;
				this.notifyVisibility('hidden');
				return;
			}
			var cameraMovedCallback = function (state) {
				mapView.queryStoreLocator(this.externalQueryListener, this.resultStoreList, this.storeId, false, this.listLang);
			}.bind(this);
			this.mCameraCallback = cameraMovedCallback;
			cameraMovedCallback();
		}
	}
	getItemsList() {
		return this.resultStoreList;
	}
	getItemsSource() {
		return this.tvectorSources.get(0);
	}
	setPointOfInterestAndRadius(coords, radius) {
		super.setPointOfInterestAndRadius(coords, radius);
		if (this.callbackquery) {
			this.mCameraCallback = undefined;
			this.geographicList = new gem.core.RectangleGeographicAreaList();
			let geographicArea = new Module.RectangleGeographicArea();
			let offset = (this.radius / 6371) * 57.295779513;
			let topLeft = {
				latitude: this.centerPoint.latitude + offset,
				longitude: this.centerPoint.longitude - offset,
				altitude: this.centerPoint.altitude,
				bearing: this.centerPoint.bearing
			};
			let bottomRight = {
				latitude: this.centerPoint.latitude - offset,
				longitude: this.centerPoint.longitude + offset,
				altitude: this.centerPoint.altitude,
				bearing: this.centerPoint.bearing
			};
			geographicArea.setTopLeft(topLeft);
			geographicArea.setBottomRight(bottomRight);
			this.geographicList.push_back(geographicArea);
			this.mapView.centerOnArea(topLeft, bottomRight, 0);
			this.mapView.queryStoreLocator(this.externalQueryListener, this.resultStoreList, this.storeId, false, this.listLang, this.geographicList);
		}
	}
	notifySearchSelection(item) {
		if (item === undefined) {
			if (this.tvectorSources.size() > 0) {
				this.mapView.removeVectorSource(this.tvectorSources.get(0));
			}
			this.notifyVisibility('hidden');
			// Free memory of previous result, if any
			this.tvectorSources.delete();
			this.tvectorSources = undefined;
			this.resultStoreList.delete();
			if (this.selectedItem) {
				this.selectedItem.remove();
				this.selectedItem = undefined;
			}
			this.resultStoreList = new gem.core.StoreLocationList();
			if (this.listChangedNotifier) {
				this.listChangedNotifier(this.externalRenderObj.name, this.mapView, this.storeId);
			}
			return;
		}
		this.notifyVisibility('visible');
		if (this.radius === undefined)
			this.radius = 10;
		this.setPointOfInterestAndRadius(item.getCoordinates(), this.radius);
	}
	notifyChangeFilterData(type, name, values) {
		switch (type) {
			case 'distance': {
				this.radius = parseFloat(values);
				if (this.centerPoint) {
					if (this.selectedItem) {
						this.selectedItem.remove();
						this.selectedItem = undefined;
					}
					this.setPointOfInterestAndRadius(this.centerPoint, this.radius);
				}
				break;
			}
			case 'languages': {
				let pt = parseInt(values[0].value);
				if (this.listLang) {
					this.listLang.delete();
				}
				this.listLang = new gem.core.GemStringList();
				this.listLang.push_back(this.locationConfigurationList.get(pt).getValue());
				if (this.selectedItem) {
					this.selectedItem.remove();
					this.selectedItem = undefined;
				}
				this.setPointOfInterestAndRadius(this.centerPoint, this.radius);
				break;
			}
			case 'category':
				if (this.listLang) {
					this.listLang.delete();
				}
				this.listLang = new gem.core.GemStringList();
				for (let i = 0; i < values.length; i++) {
					if (values[i].key === 'language')
						this.listLang.push_back(values[i].value);
				}
				if (!values.length)
					this.listLang.push_back('all');
				if (this.centerPoint && this.radius)
					this.setPointOfInterestAndRadius(this.centerPoint, this.radius);
				break;
		}

	}
	requestFieldValues(key, cb) {
		switch (key) {
			case 'languages': {
				var locationConfigurationList = new gem.core.StoreLocationLangList();
				var callbackqueryLang = function (reason) {
					this.locationConfigurationList = locationConfigurationList;
					cb(this.locationConfigurationList);
				}.bind(this);
				var externalQueryListener2 = new gem.d3Scene.ExternalQueryListener(callbackqueryLang);
				this.mapView.queryStoreLocatorConfiguration(externalQueryListener2, this.storeId, locationConfigurationList);
			}
				break;
		}
	}

}

/**
 * @callback callbackListItem
 * @param {HTMLDivElement} elMarker - html parent element
 * @param {object} properties - json properties from data source item associated to the list item
 */

/**
 * Control for displaying an interactive data source list
 * @class gem.control.ListControl
 * 
 * @param {object} initOptions
 * @param {string} [initOptions.container] - id of HTML parent element
 * @param {gem.control.BaseControl} initOptions.sourceControl - Linked data source control
 * @param {string} [initOptions.menuName] - Menu list name
 * @param {boolean} [initOptions.displayCount = true] - display information about number of items 
 * @param {number} [initOptions.itemsToLoadAtATime = 20] - no. of list items to load a time when scrolling
 * @param {string[]} initOptions.titleProperties - data source item properties to use for list item title  
 * @param {string[]} initOptions.detailsProperties - data source item properties to use for list item details
 * @param {string} initOptions.imageProperty - data source item property that contains an image url
 * @param {bool} [initOptions.displayCount = true] - display list items count
 * @param {number} [initOptions.itemsToLoadAtATime = 20] - number of list items to load at a time
 * @param {number} [initOptions.flyToItemAltitude = 1000] - set marker fly to altitude in meters
 * @param {object} [initOptions.cssClasses] - specify custom style rules for list elements
 *   @param {object} [initOptions.cssClasses.divMenu] 
 *     @param {string} [initOptions.cssClasses.divMenu.className = 'gem-markers-menu']
 *     @param {string} [initOptions.cssClasses.divMenu.type = 'div']
 *   @param {object} [initOptions.cssClasses.listHeader]
 *     @param {string} [initOptions.cssClasses.listHeader.className = 'gem-markers-menu-name']
 *     @param {string} [initOptions.cssClasses.listHeader.type = 'div']
 *   @param {object} [initOptions.cssClasses.divList]
 *     @param {string} [initOptions.cssClasses.divList.className = 'gem-markers-menu-list']
 *     @param {string} [initOptions.cssClasses.divList.type = 'div']
 *   @param {object} [initOptions.cssClasses.divItem]
 *     @param {string} [initOptions.cssClasses.divItem.className = 'gem-marker-list-item']
 *     @param {string} [initOptions.cssClasses.divItem.type = 'div']
 *	 @param {object} [initOptions.cssClasses.divItemImage]
 *     @param {string} [initOptions.cssClasses.divItemImage.className = 'gem-marker-list-item-image']
 *     @param {string} [initOptions.cssClasses.divItemImage.type = 'div']
 *   @param {object} [initOptions.cssClasses.divItemContent]
 *     @param {string} [initOptions.cssClasses.divItemContent.className = 'gem-marker-list-item-content']
 *     @param {string} [initOptions.cssClasses.divItemContent.type = 'div']
 *   @param {object} [initOptions.cssClasses.divItemTitle]
 *     @param {string} [initOptions.cssClasses.divItemTitle.className = 'gem-marker-list-item-title']
 *     @param {string} [initOptions.cssClasses.divItemTitle.type = 'div']
 *   @param {object} [initOptions.cssClasses.divItemNoResultsCss]
 *     @param {string} [initOptions.cssClasses.divItemNoResultsCss.className = 'gem-marker-list-no-results']
 *     @param {string} [initOptions.cssClasses.divItemNoResultsCss.type = 'div']
 *   @param {object} [initOptions.cssClasses.divListCountCss]
 *     @param {string} [initOptions.cssClasses.divListCountCss.className = 'gem-markers-list-count-info']
 *     @param {string} [initOptions.cssClasses.divListCountCss.type = 'div']
 *   @param {object} [initOptions.cssClasses.divListLoaderCss]
 *     @param {string} [initOptions.cssClasses.divListLoaderCss.className = 'gem-marker-list-loader']
 *     @param {string} [initOptions.cssClasses.divListLoaderCss.type = 'div']
 * @param {string} [initOptions.selectedItemClass] - css class for selected marker item
 * @param {callbackListItem} [initOptions.populateItemFunction] - fully customize the list item
 */
gem.control.ListControl = class ListControl extends gem.control.BaseControl {
	constructor(initOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);

		const defaultListOptions = {
			sourceControl: undefined,
			sortMode: gem.control.LISTSORTMODE.DISTANCE,
			container: "gem-markers-menu-container",
			menuName: "",
			titleProperties: "name",
			detailsProperties: "",
			imageProperty: "",
			displayCount: true,
			itemsToLoadAtATime: 20,
			flyToItemAltitude: 1000,
			cssClasses: {
				divMenu: {
					className: "gem-markers-menu",
					type: "div"
				},
				listHeader: {
					className: "gem-markers-menu-name",
					type: "div"
				},
				divList: {
					className: "gem-markers-menu-list",
					type: "div"
				},
				divItem: {
					className: "gem-marker-list-item",
					type: "div"
				},
				divItemImage: {
					className: "gem-marker-list-item-image",
					type: "div"
				},
				divItemContent: {
					className: "gem-marker-list-item-content",
					type: "div"
				},
				divItemTitle: {
					className: "gem-marker-list-item-title",
					type: "div"
				},
				divItemNoResultsCss: {
					className: "gem-marker-list-no-results",
					type: "div"
				},
				divListCountCss: {
					className: "gem-markers-list-count-info",
					type: "div"
				},
				divListLoaderCss: {
					className: "gem-marker-list-loader",
					type: "div"
				}
			},
			selectedItemClass: "gem-marker-selected",
			populateItemFunction: undefined /*function (elMarker, properties)*/
		}
		this.options = defaultListOptions;
		if (initOptions)
			this.options = Object.assign({}, defaultListOptions, initOptions);
		if (initOptions && initOptions.cssClasses)
			this.options.cssClasses = Object.assign({}, defaultListOptions.cssClasses, initOptions.cssClasses);

		if (!this.options.sourceControl) {
			console.log("need to specify a data source control");
			return;
		}

		this.control = this.options.sourceControl;
		this.control.registerItemSelectionListener(this.itemSelectionNotifier.bind(this));
		if (this.control.loadingMethod === gem.control.TConntrolItemUpdateMode.LIST)
			this.control.registerListChangedNotifier(this.updateList.bind(this));
		else {
			this.control.registerListChangedNotifier(this.updateIndividualList.bind(this));
			this.sortMode = this.options.sortMode;
		}
		this.control.registerListChangedFilterNotifier(this.updateFilteredList.bind(this));
		this.control.registerListLoadingOnNotifier(this.toggleListLoadingOn.bind(this));
		this.control.registerListLoadingOffNotifier(this.toggleListLoadingOff.bind(this));

		this.externalItemCreator = this.options.populateItemFunction;
		this.selectedItemClass = this.control.selectedItemClass;
	}
	itemSelectionNotifier(item) {
		if (!item) {
			let activeListing = this.elMenuList.getElementsByClassName('active');
			if (activeListing[0]) {
				activeListing[0].classList.remove('active');
			}
		} else {
			let markerId = item.id.replace(this.htmlIdentifier, '');
			let elMarkerItem = this.createListItem(markerId, item.coordinates, item.info.properties);
			if (elMarkerItem) {
				this.elMenuList.appendChild(elMarkerItem);
				elMarkerItem.classList.add('active');
				elMarkerItem.scrollIntoView({
					behaviour: 'smooth',
					block: 'nearest',
					inline: 'nearest'
				});
			} else {
				let activeListing = this.elMenuList.getElementsByClassName('active');
				if (activeListing[0]) {
					activeListing[0].classList.remove('active');
				}
				let markerId = item.id.replace(this.htmlIdentifier, '');
				// check if list item exists 
				let elMarkerItem = document.getElementById(this.options.cssClasses.divItem.className + markerId);
				if (elMarkerItem) {
					elMarkerItem.classList.add('active');
					elMarkerItem.scrollIntoView({
						behaviour: 'smooth',
						block: 'nearest',
						inline: 'nearest'
					});
				}
			}
		}
	}
	initControl(parentDiv, mapView) {
		if (this.elMenuList !== undefined)
			return;
		this.vectorItemsQuery = new Map();
		this.intersectCount = 0;

		if (this.options.container) {
			this.elementDiv = document.getElementById(this.options.container);

			//create list
			if (this.options.cssClasses.divList.className) {
				this.elMenuList = document.createElement(this.options.cssClasses.divList.type);
				this.elMenuList.className = this.options.cssClasses.divList.className;
				this.elMenuList.id = this.options.cssClasses.divList.className;
				// create markers menu container
				if (this.options.cssClasses.divMenu.className) {
					let elMarkersMenu = document.createElement(this.options.cssClasses.divMenu.type);
					elMarkersMenu.className = this.options.cssClasses.divMenu.className;
					// menu name
					if (this.options.cssClasses.listHeader.className && this.options.menuName && this.options.menuName.length) {
						this.elMenuName = document.createElement(this.options.cssClasses.listHeader.type);
						this.elMenuName.className = this.options.cssClasses.listHeader.className;
						this.elMenuName.id = this.options.cssClasses.listHeader.className;
						let headingMenuName = document.createElement('h2');
						headingMenuName.innerHTML = this.options.menuName;
						this.elMenuName.appendChild(headingMenuName);
						elMarkersMenu.appendChild(this.elMenuName);
					}
					elMarkersMenu.appendChild(this.elMenuList);
					this.elementDiv.appendChild(elMarkersMenu);
					// update this here, otherwise menu name clientHeight is always 0
					if (this.elMenuName)
						this.elMenuList.style.height = "calc(100% - " + (this.elMenuName.offsetHeight + 1) + "px)";
				} else
					this.elementDiv.appendChild(this.elMenuList);
			} else {
				this.elMenuList = this.elementDiv;
			}

		} else {
			let containerDiv = document.getElementById(parentDiv);
			this.elementDiv = document.createElement("div");
			this.elementDiv.classList.add("vertical-menu");
			containerDiv.appendChild(this.elementDiv);
		}
		this.mapView = mapView;
	}
	updateIndividualList(list, htmlIdentifier, mapView) {
		if (mapView && this.elMenuList === undefined)
			this.initControl(undefined, mapView);
		this.htmlIdentifier = htmlIdentifier;
		//this.control.externalRenderObj.registerMouseClickCallback(this.mouseClick.bind(this));
		while (this.elMenuList.firstChild) this.elMenuList.removeChild(this.elMenuList.lastChild);
		if (this.options.displayCount) {
			let elCountInfo = document.getElementsByClassName(this.options.cssClasses.divListCountCss.className);
			if (elCountInfo.length > 0)
				elCountInfo[0].parentNode.removeChild(elCountInfo[0]);
		}

		if (!list.size) {
			let elNoResults = this.createNoResultsItem();
			if (elNoResults)
				this.elMenuList.appendChild(elNoResults);
			return;
		}

		function logMapElements(value, key, map) {
			let it = value;
			let markerId = key;
			let markerCoord = it.marker.getCoordinatesAt ? it.marker.getCoordinatesAt(0).get(0) : it.marker.getCoordinates();
			let jsonProps = undefined;
			if (it.marker.getInfo()) {
				try {
					let markerInfo = JSON.parse(it.marker.getInfo());
					if (markerInfo && markerInfo.properties)
						jsonProps = markerInfo.properties;
					else if (markerInfo && markerInfo.parameters) {
						jsonProps = {};
						for (const param of markerInfo.parameters.keyvals) {
							jsonProps[param.key] = param.value;
						}
					}
				} catch (e) {
					console.log("List control: error in Json string.");
					return false;
				}
			}
			let elMarkerItem = this.createListItem(markerId, markerCoord, jsonProps);
			if (elMarkerItem)
				this.elMenuList.appendChild(elMarkerItem);
		}
		list.forEach(logMapElements.bind(this));

		this.setActiveListItem();
		if (this.options.displayCount)
			this.createDisplayCount(list.size);
	}
	updateList(htmlIdentifier, mapView, storeId) {
		if (this.elMenuList === undefined)
			this.initControl(undefined, mapView);
		this.intersectCount = 0;
		this.storeId = storeId;
		// clean-up previous
		while (this.elMenuList.firstChild) this.elMenuList.removeChild(this.elMenuList.lastChild);
		if (this.options.displayCount) {
			let elCountInfo = document.getElementsByClassName(this.options.cssClasses.divListCountCss.className);
			if (elCountInfo.length > 0)
				elCountInfo[0].parentNode.removeChild(elCountInfo[0]);
		}

		this.htmlIdentifier = htmlIdentifier;
		let source = this.control.getItemsList();
		try {
			if (!source || !source.size()) {
				let elNoResults = this.createNoResultsItem();
				if (elNoResults)
					this.elMenuList.appendChild(elNoResults);
				return;
			}
		} catch (e) {
			console.log("list control error ", e);
		}

		// list count info
		if (this.options.displayCount) {
			let markersInview = source.getTotalCount ? source.getTotalCount() : undefined;
			let markersMenu = document.getElementsByClassName(this.options.cssClasses.divMenu.className);
			if (markersMenu && markersMenu.length > 0) {
				let elListCount = markersMenu[0].insertBefore(document.createElement(this.options.cssClasses.divListCountCss.type), this.elMenuList);
				elListCount.className = this.options.cssClasses.divListCountCss.className;
				let elCountInfo = elListCount.appendChild(document.createElement('span'));
				if (markersInview) {
					elCountInfo.textContent = "Showing " + source.size().toLocaleString() + " of " + markersInview.toLocaleString() +
						(source.size() > 1 ? " results" : " result") + " in this area.";
				} else {
					elCountInfo.textContent = "Showing " + source.size().toLocaleString() +
						(source.size() > 1 ? " results." : " result.");
				}
				let adjustListHeight = (this.elMenuName ? this.elMenuName.clientHeight : 0) + elListCount.clientHeight;
				this.elMenuList.style.height = "calc(100% - " + adjustListHeight + "px)";
			}
		}

		// populate markers list
		let loadMarkersCount = Math.min(source.size(), this.options.itemsToLoadAtATime);
		this.vectorItemsQuery.clear();

		if (source.size() && source.getMarkerAt(0).queryInfo) {
			if (this.arrayOfMarkers)
				this.arrayOfMarkers.delete();
			this.arrayOfMarkers = new gem.core.StoreLocationList();
			for (let i = 0; i < loadMarkersCount; i++) {
				let marker = source.getMarkerAt(i);
				this.arrayOfMarkers.push_back(marker);
				marker.delete();
			}
			let cb = (vectorIt, reason, itemL) => {
				for (let i = 0; i < this.arrayOfMarkers.size(); i++) {
					let marker = this.arrayOfMarkers.get(i);
					let markerId = marker.getId();
					let markerCoord = marker.getCoordinatesAt ? marker.getCoordinatesAt(0).get(0) : marker.getCoordinates();
					let jsonProps = marker.getInfo() ? JSON.parse(marker.getInfo()).properties : undefined;
					let elMarkerItem = this.createListItem(markerId, markerCoord, jsonProps);
					if (elMarkerItem)
						this.elMenuList.appendChild(elMarkerItem);
				}
				//check if selected item exists and add the active status to the newly added list item
				if (this.control.selectedItem && this.control.selectedItemObj) {
					let markerId = this.control.selectedItemObj.id.replace(this.htmlIdentifier, '');
					let listItemId = this.divItemCss + markerId;
					let activeListItem = document.getElementById(listItemId);
					if (activeListItem)
						activeListItem.classList.add('active');
				}

				if (this.elMenuList.lastChild && this.control.getItemsList().size() > this.options.itemsToLoadAtATime) {
					this.createMenuListItemObserver(this.elMenuList.lastChild);
				}
			};
			this.arrayQuery = new gem.core.StoreLocationListenerContainer(this.arrayOfMarkers);
			this.arrayQuery.registerCallback(cb);
			this.arrayOfMarkers.queryInfo(this.mapView, this.storeId, cb);
			this.arrayQuery.mProgressListener = this.arrayOfMarkers.queryInfo(this.mapView, this.storeId, this.arrayQuery.callback.bind(this.arrayQuery));
		} else {
			for (let i = 0; i < loadMarkersCount; i++) {

				let marker = source.getMarkerAt(i);
				let markerId = marker.getId();
				let markerCoord = marker.getCoordinatesAt ? marker.getCoordinatesAt(0).get(0) : marker.getCoordinates();
				let jsonProps;
				if (marker.getInfo()) {
					try {
						let markerInfo = JSON.parse(marker.getInfo());
						if (markerInfo && markerInfo.properties)
							jsonProps = markerInfo.properties;
					} catch (e) {
						console.log("List control: error in Json string.");
					}
				}
				let elMarkerItem = this.createListItem(markerId, markerCoord, jsonProps);
				if (elMarkerItem)
					this.elMenuList.appendChild(elMarkerItem);
			}

			this.setActiveListItem();

			// add intersect observer to last item loaded
			if (this.elMenuList.lastChild && source.size() > this.options.itemsToLoadAtATime) {
				this.createMenuListItemObserver(this.elMenuList.lastChild);
			}
		}
	}
	updateFilteredList(list, htmlIdentifier, mapView) {
		if (mapView && this.elMenuList === undefined)
			this.initControl(undefined, mapView);
		this.htmlIdentifier = htmlIdentifier;
		while (this.elMenuList.firstChild) this.elMenuList.removeChild(this.elMenuList.lastChild);
		if (this.options.displayCount) {
			let elCountInfo = document.getElementsByClassName(this.options.cssClasses.divListCountCss.className);
			if (elCountInfo.length > 0)
				elCountInfo[0].parentNode.removeChild(elCountInfo[0]);
		}

		if (!list.size) {
			const elNoResults = this.createNoResultsItem();
			if (elNoResults)
				this.elMenuList.appendChild(elNoResults);
			return;
		}

		function logMapElements(value, key, map) {
			let it = value;
			let markerId = key;
			let markerCoord = it.marker.getCoordinatesAt(0).get(0);
			let jsonProps = undefined;
			if (it.marker.getInfo()) {
				try {
					let markerInfo = JSON.parse(it.marker.getInfo());
					if (markerInfo && markerInfo.properties)
						jsonProps = markerInfo.properties;
				} catch (e) {
					console.log("List control: error in Json string.");
					return false;
				}
			}
			let elMarkerItem = this.createListItem(String(markerId), markerCoord, jsonProps);
			if (elMarkerItem)
				this.elMenuList.appendChild(elMarkerItem);
		}
		list.forEach(logMapElements.bind(this));

		this.setActiveListItem();
		if (this.options.displayCount)
			this.createDisplayCount(list.size);
	}
	setActiveListItem() {
		//check if selected item exists and add the active status to the newly added list item
		if (this.control.selectedItem && this.control.selectedItemObj) {
			let markerId = this.control.selectedItemObj.id.replace(this.htmlIdentifier, '');
			let listItemId = this.options.cssClasses.divItem.className + markerId;
			let activeListItem = document.getElementById(listItemId);
			if (activeListItem)
				activeListItem.classList.add('active');
		}
	}
	createDisplayCount(listCount) {
		let markersMenu = document.getElementsByClassName(this.options.cssClasses.divMenu.className);
		if (markersMenu) {
			let elListCount = markersMenu[0].insertBefore(document.createElement('div'), this.elMenuList);
			elListCount.className = this.options.cssClasses.divListCountCss.className;
			let elCountInfo = elListCount.appendChild(document.createElement('span'));
			elCountInfo.textContent = "Showing " + listCount.toLocaleString() +
				(listCount > 1 ? " results" : " result") + " in this area.";
			let adjustListHeight = this.elMenuName.clientHeight + elListCount.clientHeight;
			this.elMenuList.style.height = "calc(100% - " + adjustListHeight + "px)";
		}
	}
	createNoResultsItem() {
		let elNoResults = document.createElement(this.options.cssClasses.divItemNoResultsCss.type);
		elNoResults.className = this.options.cssClasses.divItemNoResultsCss.className;
		let elMarkerTitle = elNoResults.appendChild(document.createElement(this.options.cssClasses.divItemTitle.type));
		elMarkerTitle.className = this.options.cssClasses.divItemTitle.className;
		elMarkerTitle.textContent = "No results found";
		let elMarkerDetails = elNoResults.appendChild(document.createElement('p'));
		elMarkerDetails.textContent = "Try zooming out or zooming in or try a different location.";
		return elNoResults;
	}
	createMenuListItemObserver(listItem) {
		let observer;
		let storesContainer = this.elMenuList;
		let options = {
			root: storesContainer,
			rootMargin: "0px",
			threshold: [0.0, 0.25, 0.5, 0.75, 1.0]
		};

		observer = new IntersectionObserver(this.handleMenuListItemIntersect.bind(this), options);
		observer.observe(listItem);
	}
	handleMenuListItemIntersect(entries, observer) {
		let source = this.control.getItemsList();
		if (!source || source.size())
			entries.forEach(entry => {
				if (entry.isIntersecting) {
					this.intersectCount++;
					// unobserve this entry target
					observer.unobserve(entry.target);
					// load next results
					let elMenuList = this.elMenuList;
					let firstStoreIdx = this.options.itemsToLoadAtATime * this.intersectCount;
					let lastStoreIdx = Math.min(source.size(), this.options.itemsToLoadAtATime * (this.intersectCount + 1));
					if (source.getMarkerAt(firstStoreIdx).queryInfo) {
						if (this.arrayOfMarkers)
							this.arrayOfMarkers.delete();
						this.arrayOfMarkers = new gem.core.StoreLocationList();
						for (let i = firstStoreIdx; i < lastStoreIdx; i++) {
							let marker = source.getMarkerAt(i);
							this.arrayOfMarkers.push_back(marker);
							marker.delete();
						}
						let cb = (vectorIt, reason, itemL) => {
							for (let i = 0; i < this.arrayOfMarkers.size(); i++) {
								let marker = this.arrayOfMarkers.get(i);
								let markerId = marker.getId();
								let markerCoord = marker.getCoordinatesAt ? marker.getCoordinatesAt(0).get(0) : marker.getCoordinates();
								let jsonProps = marker.getInfo() ? JSON.parse(marker.getInfo()).properties : undefined;
								let elMarkerItem = this.createListItem(markerId, markerCoord, jsonProps);
								if (elMarkerItem)
									this.elMenuList.appendChild(elMarkerItem);
							}
							if (source.size() > this.options.itemsToLoadAtATime * (this.intersectCount + 1))
								this.createMenuListItemObserver(this.elMenuList.lastChild);
						}
						this.arrayQuery = new gem.core.StoreLocationListenerContainer(this.arrayOfMarkers);
						this.arrayQuery.registerCallback(cb);
						this.arrayOfMarkers.queryInfo(this.mapView, this.storeId, cb);
						this.arrayQuery.mProgressListener = this.arrayOfMarkers.queryInfo(this.mapView, this.storeId, this.arrayQuery.callback.bind(this.arrayQuery));
					} else {

						for (let i = firstStoreIdx; i < lastStoreIdx; i++) {
							let marker = source.getMarkerAt(i);
							let markerId = marker.getId();
							let markerCoord = marker.getCoordinatesAt ? marker.getCoordinatesAt(0).get(0) : marker.getCoordinates();
							let jsonProps = marker.getInfo() ? JSON.parse(marker.getInfo()).properties : undefined;
							let elMarkerItem = this.createListItem(markerId, markerCoord, jsonProps);
							if (elMarkerItem)
								elMenuList.appendChild(elMarkerItem);
						}

						// observe next entry
						if (source.size() > this.options.itemsToLoadAtATime * (this.intersectCount + 1))
							this.createMenuListItemObserver(this.elMenuList.lastChild);
					}
				}
			});
	}
	createListItem(markerId, markerCoord, jsonProperties) {
		// check if list item exists 
		if (document.getElementById(this.options.cssClasses.divItem.className + markerId))
			return;

		try {
			let markerProps = jsonProperties;
			let elMarkerItem = document.createElement(this.options.cssClasses.divItem.type);
			elMarkerItem.id = this.options.cssClasses.divItem.className + markerId;
			elMarkerItem.className = this.options.cssClasses.divItem.className;
			if (this.externalItemCreator) {
				this.externalItemCreator(elMarkerItem, markerProps);
			} else { // default
				// image
				if (this.options.imageProperty && this.options.imageProperty.length) {
					const strImageUrl = markerProps[this.options.imageProperty];
					if (strImageUrl && strImageUrl.length) {
						const liImage = elMarkerItem.appendChild(document.createElement(this.options.cssClasses.divItemImage.type));
						liImage.className = this.options.cssClasses.divItemImage.className;
						const img = document.createElement('img');
						img.onload = () => {
							elContent.className = this.options.cssClasses.divItemContent.className;
							elContent.style.width = '60%';
							liImage.appendChild(img);
						};
						img.onerror = () => {
							liImage.style.display = 'none';
						};
						img.src = strImageUrl;
					}
				}
				// content
				const elContent = elMarkerItem.appendChild(document.createElement(this.options.cssClasses.divItemContent.type));
				elContent.className = this.options.cssClasses.divItemContent.className;
				// title
				const elMarkerTitle = elContent.appendChild(document.createElement(this.options.cssClasses.divItemTitle.type));
				elMarkerTitle.className = this.options.cssClasses.divItemTitle.className;
				elMarkerTitle.textContent = this.control.getPropertyString(markerProps, this.options.titleProperties, ', ');
				// details
				const elMarkerDetails = elContent.appendChild(document.createElement('div'));
				elMarkerDetails.innerHTML = this.control.getPropertyString(markerProps, this.options.detailsProperties, ' &middot; ');
			}
			// event listener for mouse over
			elMarkerItem.addEventListener('mouseover', function () {
				let groupId = markerId;
				if (this.control.getItemsSource()) {
					let groupParent = this.control.getItemsSource().getGroupHeadItem(markerId);
					groupId = groupParent.getId();
				}
				let markerDivId = this.htmlIdentifier + groupId;
				let storeMarker = document.getElementById(markerDivId);
				if (storeMarker) {
					storeMarker.classList.add('active');
				}
			}.bind(this), false);
			// event listener for mouse over
			elMarkerItem.addEventListener('mouseout', function () {
				let groupId = markerId;
				if (this.control.getItemsSource()) {
					let groupParent = this.control.getItemsSource().getGroupHeadItem(markerId);
					groupId = groupParent.getId();
				}
				let markerDivId = this.htmlIdentifier + groupId;
				let storeMarker = document.getElementById(markerDivId);
				if (storeMarker) {
					storeMarker.classList.remove('active');
				}
			}.bind(this), false);
			// event listener for mouse click
			elMarkerItem.addEventListener('click', function (e) {
				e.preventDefault();
				// fly to store
				this.flyToMarker(markerId, markerCoord);
				// highlight listing in sidebar and remove highlight for all other listings
				const activeListing = this.elMenuList.getElementsByClassName('active');
				if (activeListing[0]) {
					activeListing[0].classList.remove('active');
				}
				elMarkerItem.scrollIntoView({
					behaviour: 'smooth',
					block: 'nearest',
					inline: 'nearest'
				});
				e.currentTarget.parentNode.classList.add('active');
			}.bind(this), false);

			return elMarkerItem;
		} catch (e) {
			console.log("List Control: error creating list item");
		}
	}
	retainSelectedItem(item, jsonObject, position) {
		this.selectedItemElement = item.cloneNode(true);
		if (this.selectedItemClass)
			item.className = this.selectedItemClass;
		item.id = 'selectedItem';
		item.style.display = 'none';
		item.parentElement.appendChild(this.selectedItemElement);
	}
	cameraChangedCallback() {
		if (this.mCameraCallback)
			this.mCameraCallback();
	}
	flyToMarker(markerId, markerCoords) {
		let callbackCameraChange = function (reason) {
			this.mCameraCallback = undefined;
			let markerDivId = this.htmlIdentifier + markerId;
			let storeMarker = document.getElementById(markerDivId);

			if (storeMarker) {
				const event = new MouseEvent('mouseover', {
					'view': window,
					'bubbles': true,
					'cancelable': true
				});

				storeMarker.dispatchEvent(event);
				storeMarker.click();
			}
			// else marker is in group 
		};
		let callbackFinishedFly = function (reason) {
			this.mCameraCallback = callbackCameraChange.bind(this);
		}.bind(this);
		markerCoords.altitude = this.options.flyToItemAltitude; // altitude has to be lower than max group level
		this.mapView.centerOnCoordinates(markerCoords, 2000 /*ms fly time*/, callbackFinishedFly);
	}
	toggleListLoadingOn() {
		let elCount = document.getElementsByClassName(this.options.cssClasses.divListCountCss.className);
		if (elCount.length)
			elCount[0].style.opacity = '0.5';
		let elListMenu = document.getElementsByClassName(this.options.cssClasses.divList.className);
		if (elListMenu.length) {
			elListMenu[0].style.opacity = '0.5';
			elListMenu[0].style.pointerEvents = 'none';
		}

		let listLoader = document.getElementById(this.options.cssClasses.divListLoaderCss.className);
		if (!listLoader) {
			let elListContainer = document.getElementsByClassName(this.options.cssClasses.divMenu.className);
			if (elListContainer.length)
				listLoader = this.createLoadingCircle(elListContainer[0]);
		}
		if (listLoader) {
			listLoader.style.display = '';
			listLoader.style.visibility = 'visible';
		}
	}
	toggleListLoadingOff() {
		let elCount = document.getElementsByClassName(this.options.cssClasses.divListCountCss.className);
		if (elCount.length)
			elCount[0].style.opacity = '1';
		let elListMenu = document.getElementsByClassName(this.options.cssClasses.divList.className);
		if (elListMenu.length) {
			elListMenu[0].style.opacity = '1';
			elListMenu[0].style.pointerEvents = 'auto';
		}

		let listLoader = document.getElementById(this.options.cssClasses.divListLoaderCss.className);
		if (listLoader)
			listLoader.style.display = 'none';
	}
	createLoadingCircle(parentDiv) {
		let svgns = "http://www.w3.org/2000/svg";
		let wrapperDiv = parentDiv.insertBefore(document.createElement(this.options.cssClasses.divListLoaderCss.type), parentDiv.firstChild);
		wrapperDiv.id = this.options.cssClasses.divListLoaderCss.className;
		wrapperDiv.className = this.options.cssClasses.divListLoaderCss.className;

		let svg = document.createElementNS(svgns, "svg");
		svg.classList.add("circular-loader");
		svg.setAttribute("viewBox", "25 25 50 50");
		wrapperDiv.appendChild(svg);
		let circle = document.createElementNS(svgns, 'circle');
		circle.classList.add("loader-path");
		circle.setAttributeNS(null, 'cx', 50);
		circle.setAttributeNS(null, 'cy', 50);
		circle.setAttributeNS(null, 'r', 20);
		circle.setAttributeNS(null, 'style', 'fill: none; stroke: #70c542; stroke-width: 2px;');
		svg.appendChild(circle);
		return wrapperDiv;
	}
}

/**
 * Control for displaying search nearby control
 * @class gem.control.SearchNearbyControl
 * 
 * @param {object} initSearchNearbyControlOptions
 *    @param {object} initSearchNearbyControlOptions.categoryfilter
 *    @param {string} initSearchNearbyControlOptions.container
 *    @param {gem.core.Coordinates} initSearchNearbyControlOptions.center
 *    @param {callback} initSearchNearbyControlOptions.populateItemFunction
 *    @param {callback} initSearchNearbyControlOptions.selectedItemCallback
 *    @param {gem.control.BaseControl} initSearchNearbyControlOptions.categorylistcontrol
 */
gem.control.SearchNearbyControl = class SearchNearbyControl extends gem.control.BaseControl {
	constructor(initSearchNearbyControlOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);
		if (initSearchNearbyControlOptions) {
			this.categoryfilter = initSearchNearbyControlOptions.categoryfilter;
			this.customContainer = initSearchNearbyControlOptions.container;
			this.center = initSearchNearbyControlOptions.center;
			this.populateItemFunction = initSearchNearbyControlOptions.populateItemFunction;
			this.onSelectCallback = initSearchNearbyControlOptions.selectedItemCallback;
			this.categorylistcontrol = initSearchNearbyControlOptions.categorylistcontrol;
			if (this.categorylistcontrol) {
				this.categorylistcontrol.registerListUpdatedNotifier(this.updateCategoryList.bind(this));
			}
		}
	}
	updateCenter(center) {

	}
	setControlMode(mode) {
		this.controlMode = mode;
	}
	notifySearchSelection(item) {
		if (item) {
			this.center = item.getCoordinates();
		}
		if (this.elNearbyListContainer)
			this.elNearbyListContainer.style.visibility = 'hidden';
		this.elementDiv.style.visibility = 'hidden';
	}
	initControl(parentDiv, mapView) {
		this.parentDiv = parentDiv;
		this.resultList = new gem.core.LandmarkList();
		this.elementDiv = document.createElement("div");
		if (this.controlMode && this.controlMode === 'dependend')
			this.elementDiv.style.visibility = 'hidden';
		this.elementDiv.style.overflow = 'auto';
		this.elementDiv.classList.add("gem-markers-menu-list");
		let containerDiv;
		if (this.customContainer)
			containerDiv = document.getElementById(this.customContainer);
		else {
			this.elNearbyListContainer = document.createElement('div');
			this.elNearbyListContainer.className = 'gem-nearby-lmks-list-container';
			let lstchild = document.getElementById(parentDiv).lastChild;
			if (lstchild && lstchild.id !== 'buttonCopyright')
				this.elNearbyListContainer.style.top = lstchild.offsetTop + lstchild.offsetHeight + 2 + 'px';
			document.getElementById(parentDiv).appendChild(this.elNearbyListContainer);
			containerDiv = this.elNearbyListContainer;
			if (this.controlMode && this.controlMode === 'dependend')
				this.elNearbyListContainer.style.visibility = 'hidden';
		}

		containerDiv.appendChild(this.elementDiv);

		//var cameraMovedCallback = function (state) {
		//	this.center = this.mapView.getCursorPositionWGS();
		//	this.updateNearby();
		//}.bind(this);
		if (this.center === undefined) {
			this.center = mapView.getCursorPositionWGS();
		}
		//mapView.registerCameraChangeStateCallback(cameraMovedCallback);
		this.mapView = mapView;
	}
	preRenderCallback() {
		if (this.selectedButton && this.selectedLandmark) {
			let coordsPoint = this.selectedLandmark.getCoordinates();
			let screenCoords = this.mapView.transformWgsToScreen(coordsPoint);
			let dx = (screenCoords.x - this.selectedButton.halfsizeX);
			let dy = (screenCoords.y - this.selectedButton.halfsizeY);
			this.selectedButton.style.transform = "translate3d(" + dx + "px," + dy + "px,0px)";
		}
	}
	showSelectedBubble() {
		if (!this.selectedButton || !this.selectedLandmark)
			return;
		let coordsPoint = this.selectedLandmark.getCoordinates();
		let screenCoords = this.mapView.transformWgsToScreen(coordsPoint);
		let dx = (screenCoords.x - this.selectedButton.halfsizeX);
		let dy = (screenCoords.y - this.selectedButton.halfsizeY);
		this.selectedButton.style.transform = "translate3d(" + dx + "px," + dy + "px,0px)";
		this.selectedButton.style.visibility = '';
		this.selectedBubble = document.getElementById('gem-landmark-bubble');
		if (!this.selectedBubble) {
			this.selectedBubble = this.selectedButton.appendChild(document.createElement('div'));
			this.selectedBubble.id = 'gem-landmark-bubble';
			this.selectedBubble.className = 'gem-landmark-bubble';
			let elLmkBubbleText = this.selectedBubble.appendChild(document.createElement('div'));
			elLmkBubbleText.className = 'gem-landmark-bubble-text';
			let elLmkBubbleTitle = elLmkBubbleText.appendChild(document.createElement('div'));
			elLmkBubbleTitle.id = 'gem-landmark-bubble-title';
			elLmkBubbleTitle.className = 'gem-landmark-bubble-title';
		}
		document.getElementById('gem-landmark-bubble-title').innerHTML = this.selectedLandmark.getName();
		this.selectedBubble.style.visibility = '';
	}
	updateNearby() {

		if (this.selectedFilter) {
			if (this.selectedFilter.size() !== 0) {
				if (this.resultList === undefined) {
					this.resultList = new gem.core.LandmarkList();
				}
				let resultTouched = new gem.core.LandmarkList();
				let callbackFunction = function (reason) {
					this.elementDiv.style.visibility = 'visible';
					if (this.elNearbyListContainer)
						this.elNearbyListContainer.style.visibility = 'visible';
					if (this.customContainer)
						document.getElementById(this.customContainer).style.display = 'block';
					console.log("Reason is " + reason);
					// Set the result highlight color (rgba) to red
					this.mapView.activateHighlight(this.resultList, {
						r: 255,
						g: 0,
						b: 0,
						a: 255
					},
						gem.d3Scene.EHighlightOptions.EHO_ShowLandmark.value |
						gem.d3Scene.EHighlightOptions.EHO_Overlap.value | gem.d3Scene.EHighlightOptions.EHO_Group.value, -1);
					let cb = function (resultedTouches) {
						if (resultTouched.size()) {
							let pickedLmkId = resultTouched.get(0).getLandmarkId();
							let pickedLmkName = resultTouched.get(0).getName();
							let pickedLmkCoords = resultTouched.get(0).getCoordinates();
							let pickedLmkDescr = resultTouched.get(0).getDescription();

							let idx;
							for (let i = 0; i < this.resultList.size(); i++) {
								let resultCoords = this.resultList.get(i).getCoordinates();
								// use this check because landmark ids are not unique (they are the landmark type codes)
								if (this.resultList.get(i).getLandmarkId() == pickedLmkId &&
									this.resultList.get(i).getName() == pickedLmkName &&
									this.resultList.get(i).getDescription() == pickedLmkDescr &&
									resultCoords.longitude === pickedLmkCoords.longitude &&
									resultCoords.latitude === pickedLmkCoords.latitude) {
									this.selectedLandmark = this.resultList.get(i);
									idx = i;
									// activate list item
									let selectedListItem = document.getElementById("nearbyList" + idx);
									if (selectedListItem) {
										let selectedItems = document.getElementsByClassName(selectedListItem.className + ' active');
										if (selectedItems && selectedItems.length)
											selectedItems[0].classList.remove('active');
										selectedListItem.classList.add('active');
										selectedListItem.scrollIntoView({
											behaviour: 'smooth',
											block: 'nearest',
											inline: 'nearest'
										});
									}
									this.showSelectedBubble();

									break;
								}
							}
						}
					}.bind(this);
					this.mapView.registerLandmarkClickedEvent(resultTouched, cb);
					this.updateResultList();
				}.bind(this);
				// Search around specified coordinates
				gem.places.Search.searchPoiCategory(this.selectedFilter, this.center, callbackFunction, this.resultList, 4000);
			} else {
				while (this.elementDiv.firstChild) {
					this.elementDiv.firstChild.remove();
				}
				if (this.elNearbyListContainer)
					this.elNearbyListContainer.style.visibility = 'hidden';
				this.elementDiv.style.visibility = 'hidden';
				if (this.customContainer)
					document.getElementById(this.customContainer).style.visibility = 'none';
				this.mapView.deactivateHighlight();
			}
		}
	}
	updateResultList() {
		while (this.elementDiv.firstChild) {
			this.elementDiv.firstChild.remove();
		}
		if (!this.selectedButton) {
			this.selectedButton = document.createElement('div');
			this.selectedButton.className = 'gem-highlight-landmark-icon';
			document.getElementById(this.parentDiv).appendChild(this.selectedButton);
		}
		this.selectedButton.style.top = "0px";
		this.selectedButton.style.left = "0px";
		this.selectedButton.style.width = "30px";
		this.selectedButton.style.height = "30px";
		this.selectedButton.style.visibility = "hidden";
		this.selectedButton.halfsizeY = 20;
		this.selectedButton.halfsizeX = 20;
		let resultCoordinates = [];
		for (let i = 0; i < this.resultList.size(); i++) {

			let pElement = document.createElement("div");
			let resultLandmark = this.resultList.get(i);
			resultCoordinates.push(resultLandmark.getCoordinates());
			if (this.populateItemFunction) {
				this.populateItemFunction(pElement, resultLandmark);
			} else {
				pElement.className = 'gem-marker-list-item';
				pElement.innerText = resultLandmark.getFormatedName();
			}
			let id = i;
			pElement.id = "nearbyList" + id;
			pElement.style.cursor = 'pointer';
			pElement.onmouseenter = function () {
				if (this.selectedBubble)
					this.selectedBubble.style.visibility = 'hidden';
				this.selectedButton.style.visibility = 'visible';
				this.selectedLandmark = resultLandmark;

				let groupId = this.mapView.getHighlightGroupItemIndex(id);
				let groupLandmark = this.resultList.get(groupId);
				let coordsPoint = groupLandmark.getCoordinates();
				let screenCoords = this.mapView.transformWgsToScreen(coordsPoint);
				let dx = (screenCoords.x - this.selectedButton.halfsizeX);
				let dy = (screenCoords.y - this.selectedButton.halfsizeY);
				this.selectedButton.style.transform = "translate3d(" + dx + "px," + dy + "px,0px)";
				let bitmapContainer = new gem.core.BitmapContainer(40, 40);
				resultLandmark.getImage(bitmapContainer);
				let canvasIcon = document.createElement('canvas');
				canvasIcon.className = "scale";
				gem.core.App.setCanvas(bitmapContainer.toImageData(), 40, 40, canvasIcon);
				this.selectedButton.appendChild(canvasIcon);
			}.bind(this);
			pElement.onmouseleave = function () {
				this.selectedButton.style.visibility = 'hidden';
				while (this.selectedButton.firstChild)
					this.selectedButton.removeChild(this.selectedButton.firstChild);
				this.mapView.activateHighlight(this.resultList, {
					r: 255,
					g: 0,
					b: 0,
					a: 255
				},
					gem.d3Scene.EHighlightOptions.EHO_ShowLandmark.value | gem.d3Scene.EHighlightOptions.EHO_NoFading.value |
					gem.d3Scene.EHighlightOptions.EHO_Overlap.value | gem.d3Scene.EHighlightOptions.EHO_Group.value, -1);
				let selectedItems = this.elementDiv.getElementsByClassName('active');
				if (selectedItems && selectedItems.length) {
					let idx = parseInt(selectedItems[0].id.replace('nearbyList', ''));
					this.selectedLandmark = this.resultList.get(idx);
					let coordsPoint = this.selectedLandmark.getCoordinates();
					let screenCoords = this.mapView.transformWgsToScreen(coordsPoint);
					let dx = (screenCoords.x - this.selectedButton.halfsizeX);
					let dy = (screenCoords.y - this.selectedButton.halfsizeY);
					this.selectedButton.style.transform = "translate3d(" + dx + "px," + dy + "px,0px)";
					this.selectedButton.style.visibility = 'visible';
					this.showSelectedBubble();
				}
			}.bind(this);
			pElement.onclick = function () {
				this.selectedButton.style.visibility = 'hidden';
				this.selectedLandmark = resultLandmark;
				let selectedItems = document.getElementsByClassName(pElement.className + ' active');
				if (selectedItems && selectedItems.length)
					selectedItems[0].classList.remove('active');
				pElement.classList.add('active');

				let listWithoutI = new gem.core.LandmarkList();
				for (let j = 0; j < this.resultList.size(); j++) {
					if (j !== id)
						listWithoutI.push_back(this.resultList.get(j));
				}
				this.mapView.activateHighlight(listWithoutI, {
					r: 255,
					g: 0,
					b: 0,
					a: 255
				},
					gem.d3Scene.EHighlightOptions.EHO_ShowLandmark.value | gem.d3Scene.EHighlightOptions.EHO_NoFading.value | gem.d3Scene.EHighlightOptions.EHO_Group.value |
					gem.d3Scene.EHighlightOptions.EHO_Overlap.value, -1);

				if (this.onSelectCallback)
					this.onSelectCallback(resultLandmark);
				else {
					this.showSelectedBubble();
					let coords = resultLandmark.getCoordinates();
					coords.altitude = 200;
					this.mapView.centerOnCoordinates(coords, 2000);
				}
			}.bind(this);
			this.elementDiv.appendChild(pElement);
		}

		// zoom to bounding box
		let resultsBBox = gem.core.App.getBoundingBoxForCoordinates(resultCoordinates);
		if (!resultsBBox.isEmpty) {
			// adjust bbox
			let mapCanvas = document.getElementById(this.parentDiv);
			if (mapCanvas) {
				let adjustRectangle = {
					x: mapCanvas.offsetLeft + mapCanvas.offsetWidth / 4,
					y: mapCanvas.offsetTop + mapCanvas.offsetHeight / 4,
					width: mapCanvas.offsetWidth / 2,
					height: mapCanvas.offsetHeight / 2
				};
				this.mapView.centerOnArea(resultsBBox.leftTop, resultsBBox.rightBottom, 2000, adjustRectangle);
			} else
				this.mapView.centerOnArea(resultsBBox.leftTop, resultsBBox.rightBottom, 2000);
		}
	}
	updateCategoryList(categoryList) {
		this.selectedFilter = categoryList;
		this.updateNearby();
	}
}

gem.control.POICategoryListControl = class POICategoryListControl extends gem.control.BaseControl {

	constructor(initOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);
		if (initOptions) {
			this.customContainer = initOptions.container;
		}

	}
	resetList() {
		while (this.elementDiv.firstChild) {
			this.elementDiv.firstChild.remove();
		}
		this.arrayList.clear();
		let i = 0;
		for (i = 0; i < this.listItems.size(); i++) {
			let categoryItem = this.listItems.get(i);
			let pElement = document.createElement("div");
			pElement.className = 'gem-marker-list-item';
			pElement.style.cursor = 'pointer';

			var bitmapContainer = new gem.core.BitmapContainer(20, 20);

			let poiIcon = document.createElement("CANVAS");
			poiIcon.style = "margin-top: auto;margin-bottom: auto;";
			categoryItem.getImage(bitmapContainer);
			gem.core.App.setCanvas(bitmapContainer.toImageData(), 20, 20, poiIcon);
			pElement.appendChild(poiIcon);
			bitmapContainer.delete();
			let textPart = document.createElement('SPAN');
			textPart.innerText = categoryItem.getName();
			pElement.appendChild(textPart);
			pElement.onclick = function () {
				if (pElement.getElementsByTagName('SPAN').length > 1) {
					let elementSpan = pElement.getElementsByTagName('SPAN')[1];
					pElement.removeChild(elementSpan);
					this.arrayList.delete(categoryItem.getId());
					this.updateList();
				} else {
					var x = document.createElement("SPAN");
					x.innerHTML = '&#10003';
					pElement.appendChild(x);
					this.arrayList.set(categoryItem.getId(), categoryItem);
					this.updateList();
				}
			}.bind(this);
			this.elementDiv.appendChild(pElement);
		}
	}
	initControl(parentDiv, mapView) {
		this.selectedFilter = new gem.core.LandmarkCategoryList();
		this.elementDiv = document.createElement("div");
		this.elementDiv.classList.add("gem-markers-menu-list");
		let containerDiv = document.getElementById(this.customContainer ? this.customContainer : parentDiv);
		containerDiv.appendChild(this.elementDiv);
		this.mapView = mapView;
		this.arrayList = new Map();
		this.listItems = gem.content.Manager.getGenericCategories();
		this.resetList();
		if (this.controlMode && this.controlMode === 'dependend') {
			this.elementDiv.style.visibility = 'hidden';
			if (this.customContainer)
				document.getElementById(this.customContainer).style.display = 'none';
		}
	}
	registerListUpdatedNotifier(pListNotifier) {
		this.pListNotifier = pListNotifier;
	}
	updateList() {
		if (this.pListNotifier) {
			let i = 0;
			this.selectedFilter.delete();
			this.selectedFilter = new gem.core.LandmarkCategoryList();

			function iterateMapElements(value, key, map) {
				this.selectedFilter.push_back(value);
			}
			this.arrayList.forEach(iterateMapElements.bind(this));
			this.pListNotifier(this.selectedFilter);
		}
	}
	setControlMode(mode) {
		this.controlMode = mode;
	}
	notifySearchSelection(item) {
		this.resetList();
		if (!item) {
			this.elementDiv.style.visibility = 'hidden';
			if (this.customContainer)
				document.getElementById(this.customContainer).style.display = 'none';
		} else {
			this.elementDiv.style.visibility = 'visible';
			if (this.customContainer)
				document.getElementById(this.customContainer).style.display = 'block';
		}
	}
	show() {
		this.elementDiv.style.visibility = 'visible';
		if (this.customContainer)
			document.getElementById(this.customContainer).style.display = 'block';
	}
	hide() {
		this.elementDiv.style.visibility = 'hidden';
		if (this.customContainer)
			document.getElementById(this.customContainer).style.display = 'none';
	}
}

/**
 * @callback callbackMarkersFeed
 * @param {number} mapCenterLon - visible map center longitude
 * @param {number} mapCenterLat - visible map center latitude
 * @param {number} mapLonMin - visible map minimum longitude
 * @param {number} mapLatMin - visible map minimum latitude
 * @param {number} mapLonMax - visible map maximum longitude
 * @param {number} mapLatMax - visible map maximum latitude
 * @param {object} filters - filters map
 *   @param {string} filters.name - unique name of the filter
 *   @param {object[]} filters.values - filter values
 *     @param {string} filter.values.key - item property key to use for filter option
 *     @param {string} filter.values.value - item property value to use for filter option
 * @param {AbortController.signal} signal - use in request to cancel if a new request has been triggered 
 */
/**
 * @callback callbackMarkersAreaSelection
 * @param {object} area - area coordinates array
 * @param {object} filters - filters map
 * @param {AbortController.signal} signal - use in request to cancel if a new request has been triggered
 */
/** Control for displaying data on a map using a server database as source
 * @class gem.control.CustomQueryAddedDataControl
 *
 * @param {string} iconUrl - path/url to marker image/icon
 * @param {object[]} [iconFilter] - filters for applying different icons to markers based on their poperties
 *    @param {string} iconFilter[].key - data source item property key
 *    @param {string} iconFilter[].value - data source item property value
 *    @param {string} iconFilter[].iconClass - css class to use for this filter
 * @param {object} [dataOptions] - options for displaying data source items
 * 	 @param {object} dataOptions.marker - options for markers display on map
 * 	   @param {string} dataOptions.marker.cssClass - specify custom marker style rules
 * 	   @param {string} dataOptions.marker.highlightClass - specify selected marker custom highlight style rules
 * 	   @param {number} [dataOptions.marker.width = 20]
 *	   @param {number} [dataOptions.marker.height = 20]
 *	   @param {number} [dataOptions.marker.hoverWidth = 25]
 *	   @param {number} [dataOptions.marker.hoverHeight = 25]
 *     @param {string} [dataOptions.marker.markerPos = 'center'] - where to place the marker relative to the item coordinate
 *     @param {callbackMarker} dataOptions.marker.markerFunction - fully customize marker appearance
 *   @param {object} dataOptions.markerBubble - options for styling the marker bubble on click/hover
 *     @param {string[]} dataOptions.markerBubble.title - data source properties to use for marker bubble title text
 *     @param {string} dataOptions.markerBubble.image - data source image url property to use for marker bubble image
 *     @param {number} [dataOptions.markerBubble.width = 240] - bubble width in px
 * 	   @param {number} [dataOptions.markerBubble.height = 200] - bubble height in px
 *     @param {bool} [dataOptions.markerBubble.enableHover = true] - disable marker bubble interaction on hover
 *	   @param {bool} [dataOptions.markerBubble.enableClick = true] - disable marker bubble interaction on click
 * 	   @param {string} dataOptions.markerBubble.markerBubbleClass - custom css class for marker bubble
 *     @param {callbackMarkerBubble} dataOptions.markerBubble.markerBubbleFunction - fully customize marker bubble
 *   @param {object} dataOptions.markerGrouping - marker grouping options
 *     @param {number} [dataOptions.markerGrouping.maxLevel = 13] - maximum map level to apply grouping ( max allowed 15 ), not available for studio data
 *	   @param {object} [dataOptions.markerGrouping.style = gem.control.MarkersGroupStyleType.default] - style to apply to marker groups
 *     @param {callbackMarkerGroup} [dataOptions.markerGrouping.markerGroupFunction] - fully customize marker groups style
 * @param {object} feedDataOptions 
 * 	 @param {bool} [feedDataOptions.populateForMapBrowse = true]
 * 	 @param {callbackMarkersFeed} feedDataOptions.markersFeedFunction - callback for adding data during map browse
 *   @param {callbackMarkersAreaSelection} feedDataOptions.markersAreaSelectionFunction - callback for adding data in the selected area
 */
gem.control.CustomQueryAddedDataControl = class CustomQueryAddedDataControl extends gem.control.AddedDataControl {
	constructor(iconUrl, iconFilter, dataOptions, feedDataOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED, gem.control.TConntrolItemUpdateMode.LIST, dataOptions);
		this.iconUrl = iconUrl;

		if (iconFilter) {
			this.iconFilter = iconFilter;
			this.iconUrl = super.iconFilter.bind(this);
		}

		const defaultFeedDataOptions = {
			populateForMapBrowse: true,
			markersFeedFunction: undefined,
			/*function(mapCenterLon, mapCenterLat, mapLonMin, mapLatMin, mapLonMax, mapLatMax, filters, signal)*/
			markersAreaSelectionFunction: undefined /*function(area, filters, signal) */
		}
		this.feedDataOptions = Object.assign({}, defaultFeedDataOptions, feedDataOptions);
		this.doneMapBrowsing = 1000; /*ms*/
		this.filterCategories = new Map();
	}

	cameraChangedCallback() {
		if (this.mCameraCallback) {
			clearTimeout(this.mapMoveTymer);
			this.mapMoveTymer = setTimeout(() => {
				if (this.mCameraCallback)
					this.mCameraCallback().catch(err => {
						console.log('camera callback cancelled', err);
					});
			}, this.doneMapBrowsing);
		}
	}

	initControl(parentDiv, mapView) {
		this.controlName = "customQueryAdded";
		this.mapView = mapView;
		this.mapView.setNorthFixedFlag(true);

		this.externalRenderObj = new gem.d3Scene.ExternalRenderer(
			this.iconUrl, this.controlName, mapView, parentDiv, this.options.marker.cssClass, this.rendererOptions);
		this.externalRenderObj.registerMouseOverCallback(this.mouseOver.bind(this));
		this.externalRenderObj.registerMouseOutCallback(this.mouseOut.bind(this));
		this.externalRenderObj.registerMouseClickCallback(this.mouseClick.bind(this));
		if (this.selectedItemClass)
			this.externalRenderObj.registerItemSelectedCallback(this.itemSelected.bind(this));
		this.externalRenderObj.registerItemAddedCallback(function (itemElementHtmlId, item, isGroup) {
			if (this.addItemToList)
				this.addItemToList(itemElementHtmlId, item, isGroup);
		}.bind(this));
		this.externalRenderObj.registerItemRemovedCallback(function (itemElementHtmlId, item, isGroup) {
			if (this.removeItemFromList)
				this.removeItemFromList(itemElementHtmlId, item, isGroup);
		}.bind(this));

		// markers finished rendering callback
		this.callbackMarkersRendered = (reason) => {
			if (this.externalRenderObj) {
				this.externalRenderObj.sourceIsPending = false;
			}
			if (this.listChangedNotifier)
				this.listChangedNotifier(this.externalRenderObj.name, this.mapView);
			if (this.listLoadingDoneNotifier)
				this.listLoadingDoneNotifier();
		};

		this.cameraMoveInProgress = false;
		let requestAborted = false;
		this.abortController = new AbortController();
		// camera moved callback
		this.cameraMovedCallback = async function () {
			if (this.cameraMoveInProgress) {
				this.abortController.abort();
				requestAborted = true;
			}
			this.cameraMoveInProgress = true;

			if (this.listLoadingNotifier)
				this.listLoadingNotifier();

			let data;
			if (this.feedDataOptions.populateForMapBrowse && this.feedDataOptions.markersFeedFunction) {
				let mapCanvas = document.getElementById(gem.core.App.initOptions.container);
				if (mapCanvas) {
					this.mapWgsCenter = this.mapView.getCursorPositionWGS();
					let mapScreenTopLeft = {
						x: mapCanvas.clientLeft,
						y: mapCanvas.clientTop
					};
					let mapScreenBottomRight = {
						x: mapCanvas.clientLeft + mapCanvas.clientWidth,
						y: mapCanvas.clientTop + mapCanvas.clientHeight
					}
					let mapWgsTopLeft = this.mapView.transformScreenToWgs(mapScreenTopLeft);
					let mapWgsBottomRight = this.mapView.transformScreenToWgs(mapScreenBottomRight);
					this.mapWgsLonMin = mapWgsTopLeft.longitude;
					this.mapWgsLonMax = mapWgsBottomRight.longitude;
					this.mapWgsLatMin = Math.min(mapWgsTopLeft.latitude, mapWgsBottomRight.latitude);
					this.mapWgsLatMax = Math.max(mapWgsTopLeft.latitude, mapWgsBottomRight.latitude);

					this.abortController = new AbortController();
					data = await this.feedDataOptions.markersFeedFunction(this.mapWgsCenter.longitude, this.mapWgsCenter.latitude,
						this.mapWgsLonMin, this.mapWgsLatMin, this.mapWgsLonMax, this.mapWgsLatMax, this.filterCategories, this.abortController.signal);
				}
			}

			if (!requestAborted) {
				try {
					// clear previous source store data if exists
					if (this.sourcesList) {
						if (this.sourcesList.size() > 0) {
							if (this.externalRenderObj) {
								this.externalRenderObj.sourceIsPending = true;
								this.externalRenderObj.clear();
								this.externalRenderObj.destroyHasBeenCalled = undefined;
							}
							this.mapView.removeVectorSource(this.sourcesList.get(0));
						}
						this.sourcesList.delete();
						this.sourcesList = new gem.d3Scene.MarkerCollectionRefList();
					} else
						this.sourcesList = new gem.d3Scene.MarkerCollectionRefList();

					this.mapView.addGeoJsonAsCustomMarkers(JSON.stringify(data), this.controlName, this.sourcesList, this.externalRenderObj, this.options.markerGrouping.maxLevel);
					if (!data || !data.features || !data.features.length) {
						if (this.listChangedNotifier)
							this.listChangedNotifier(this.externalRenderObj.name, this.mapView);
						if (this.listLoadingDoneNotifier)
							this.listLoadingDoneNotifier();
					}
				} catch (e) {
					console.log("CustomQueryAddedDataControl error: invalid data for markers");
				}
				this.mapView.registerForNextRenderFinished(this.callbackMarkersRendered);
			} else
				requestAborted = false;

			this.cameraMoveInProgress = false;
		}.bind(this);
		this.mCameraCallback = this.cameraMovedCallback.bind(this);
		this.cameraMovedCallback();
	}

	getItemsList() {
		if (!this.sourcesList || !this.sourcesList.size())
			return undefined;
		return this.sourcesList.get(0);
	}

	getItemsSource() {
		if (!this.sourcesList || !this.sourcesList.size())
			return undefined;
		return this.sourcesList.get(0);
	}

	notifyChangeFilterData(type, name, values) {
		this.filterCategories.set(name, values);
		this.updateDataSource();
	}

	updateDataSource() {
		if (this.mCameraCallback)
			this.mCameraCallback();
		else
			this.areaSelection();
	}

	toggleCameraCallback() {
		if (this.mCameraCallback) {
			this.mCameraCallback = undefined;
		} else {
			this.mCameraCallback = this.cameraMovedCallback.bind(this);
			this.mCameraCallback();
		}
	}

	notifyAreaSelection(area, areaMarkerGroupMaxLevel) {
		this.area = area;
		this.areaMarkerGroupMaxLevel = areaMarkerGroupMaxLevel;
		this.updateDataSource();
	}

	async areaSelection() {
		let requestAborted = false;
		if (this.listLoadingNotifier)
			this.listLoadingNotifier();

		let data;
		if (this.feedDataOptions && this.feedDataOptions.markersAreaSelectionFunction) {
			this.abortController = new AbortController();
			data = await this.feedDataOptions.markersAreaSelectionFunction(this.area, this.filterCategories, this.abortController.signal);
		}
		if (!requestAborted) {
			try {
				// clear previous source store data if exists
				if (this.sourcesList) {
					if (this.sourcesList.size() > 0) {
						if (this.externalRenderObj) {
							this.externalRenderObj.sourceIsPending = true;
							this.externalRenderObj.clear();
							this.externalRenderObj.destroyHasBeenCalled = undefined;
						}
						this.mapView.removeVectorSource(this.sourcesList.get(0));
					}
					this.sourcesList.delete();
					this.sourcesList = new gem.d3Scene.MarkerCollectionRefList();
				} else
					this.sourcesList = new gem.d3Scene.MarkerCollectionRefList();

				this.mapView.addGeoJsonAsCustomMarkers(JSON.stringify(data), this.controlName, this.sourcesList, this.externalRenderObj, this.areaMarkerGroupMaxLevel);
				if (!data || !data.features || !data.features.length) {
					if (this.listChangedNotifier)
						this.listChangedNotifier(this.externalRenderObj.name, this.mapView);
					if (this.listLoadingDoneNotifier)
						this.listLoadingDoneNotifier();
				}
			} catch (e) {
				console.log("CustomQueryAddedDataControl error: invalid data for markers");
			}
			this.mapView.registerForNextRenderFinished(this.callbackMarkersRendered);
		} else
			requestAborted = false;
	}
}

/** Control for displaying data from a studio data source query around a location
 * @class gem.control.StudioQueryDataControl
 *
 * @param {number} dataSourceId - studio data source id
 * @param {string} iconUrl - path/url to marker image/icon
 * @param {object} [dataOptions] - options for displaying data source items
 * 	 @param {object} dataOptions.marker - options for markers display on map
 * 	   @param {string} dataOptions.marker.cssClass - specify custom marker style rules
 * 	   @param {string} dataOptions.marker.highlightClass - specify selected marker custom highlight style rules
 * 	   @param {number} [dataOptions.marker.width = 20]
 *	   @param {number} [dataOptions.marker.height = 20]
 *	   @param {number} [dataOptions.marker.hoverWidth = 25]
 *	   @param {number} [dataOptions.marker.hoverHeight = 25]
 *     @param {string} [dataOptions.marker.markerPos = 'center'] - where to place the marker relative to the item coordinate
 *     @param {callbackMarker} dataOptions.marker.markerFunction - fully customize marker appearance
 *   @param {object} dataOptions.markerBubble - options for styling the marker bubble on click/hover
 *     @param {string[]} dataOptions.markerBubble.title - data source properties to use for marker bubble title text
 *     @param {string} dataOptions.markerBubble.image - data source image url property to use for marker bubble image
 *     @param {number} [dataOptions.markerBubble.width = 240] - bubble width in px
 * 	   @param {number} [dataOptions.markerBubble.height = 200] - bubble height in px
 *     @param {bool} [dataOptions.markerBubble.enableHover = true] - disable marker bubble interaction on hover
 *	   @param {bool} [dataOptions.markerBubble.enableClick = true] - disable marker bubble interaction on click
 * 	   @param {string} dataOptions.markerBubble.markerBubbleClass - custom css class for marker bubble
 *     @param {callbackMarkerBubble} dataOptions.markerBubble.markerBubbleFunction - fully customize marker bubble
 *   @param {object} dataOptions.markerGrouping - marker grouping options
 *     @param {number} [dataOptions.markerGrouping.maxLevel = 13] - maximum map level to apply grouping ( max allowed 15 ), not available for studio data
 *	   @param {object} [dataOptions.markerGrouping.style = gem.control.MarkersGroupStyleType.default] - style to apply to marker groups
 *     @param {callbackMarkerGroup} [dataOptions.markerGrouping.markerGroupFunction] - fully customize marker groups style
 * @param {object[]} [iconFilter] - filters for applying different icons to markers based on their properties
 *    @param {string} iconFilter[].key - data source item property key
 *    @param {string} iconFilter[].value - data source item property value
 *    @param {string} iconFilter[].iconClass - css class to use for this filter
 */
gem.control.StudioQueryDataControl = class StudioQueryDataControl extends gem.control.AddedDataControl {
	constructor(dataSourceId, iconUrl, dataOptions, iconFilter) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED, gem.control.TConntrolItemUpdateMode.LIST, dataOptions);
		this.dataSourceId = dataSourceId;
		this.iconUrl = iconUrl;
		if (iconFilter) {
			this.iconFilter = iconFilter;
			this.iconUrl = super.iconFilter.bind(this);
		}
	}

	cameraChangedCallback() {
		if (this.mCameraCallback) {
			this.mCameraCallback();
		}
	}

	initControl(parentDiv, mapView) {
		this.controlName = "studioQueryData";

		this.sourcesList = new gem.d3Scene.MarkerCollectionRefList();
		this.externalRenderObj = new gem.d3Scene.ExternalRenderer(
			this.iconUrl, this.controlName, mapView, parentDiv, this.options.marker.cssClass, this.rendererOptions);
		this.externalRenderObj.registerMouseOverCallback(this.mouseOver.bind(this));
		this.externalRenderObj.registerMouseOutCallback(this.mouseOut.bind(this));
		this.externalRenderObj.registerMouseClickCallback(this.mouseClick.bind(this));
		if (this.selectedItemClass)
			this.externalRenderObj.registerItemSelectedCallback(this.itemSelected.bind(this));
		this.externalRenderObj.registerItemAddedCallback(function (itemElementHtmlId, item) {
			if (this.itemAddedNotifier)
				this.itemAddedNotifier(itemElementHtmlId, item);
		}.bind(this));
		this.externalRenderObj.registerItemRemovedCallback(function (itemElementHtmlId, item) {
			if (this.itemRemovedNotifier)
				this.itemRemovedNotifier(itemElementHtmlId, item);
		}.bind(this));

		this.mapView = mapView;

		// overlay tiles db search request	
		let urlService = "";
		let serviceIds = gem.content.Manager.getServicesIds();
		for(const id of serviceIds)
		{
			if (gem.content.Manager.getServiceName(id) === "Overlays")
			{
				urlService = gem.content.Manager.getCustomUrlList(id);
				break;
			}
		}
		if (!urlService || !urlService.size()) {
			return;
		}
		this.strServiceUrl = urlService.get(0) + "/overlays/search/";

		this.filterCategories = new Map(); /* key - filter name, values - selected filter values */
		this.distanceRadius = 10; /*km*/
	}

	getItemsList() {
		if (!this.sourcesList || !this.sourcesList.size())
			return undefined;
		return this.sourcesList.get(0);
	}

	getItemsSource() {
		if (!this.sourcesList || !this.sourcesList.size())
			return undefined;
		return this.sourcesList.get(0);
	}

	notifyChangeFilterData(type, name, values) {
		switch (type) {
			case 'category':
				if (values && values.length > 0) {
					this.filterCategories.set(name, values);
				}
				break;
			case 'distance':
				if (values && values.length && !isNaN(values)) {
					this.distanceRadius = values;
				}
		}
		this.updateDataSource();
	}

	updateDataSource() {
		if (!this.refLocation) {
			console.log("no reference location");
			return;
		}

		// check reference location coords validity
		let refCoordinates = this.refLocation.getCoordinates();
		if (refCoordinates.latitude < -90 || refCoordinates.latitude > 90 ||
			refCoordinates.longitude < -180 || refCoordinates > 180) {
			console.log("invalid coords");
			return;
		}

		// clear previous store data if exists
		if (this.sourcesList) {
			if (this.sourcesList.size() > 0)
			{
				if (this.externalRenderObj) {
					this.externalRenderObj.sourceIsPending = true;
					this.externalRenderObj.clear();
					this.externalRenderObj.destroyHasBeenCalled = undefined;
				}
				this.mapView.removeVectorSource(this.sourcesList.get(0));
			}
			this.sourcesList.delete();
			this.sourcesList = new gem.d3Scene.MarkerCollectionRefList();
		}

		var searchUrl = new URL(this.strServiceUrl);
		let strfilter = '';
		if (this.filterCategories && this.filterCategories.size) {
			for (const [name, filters] of this.filterCategories.entries()) {
				if (filters) {
					strfilter += '|';
					for (let i = 0; i < filters.length; i++) {
						if (!filters[i] || !filters[i].key || !filters[i].key.length)
							continue;

						strfilter += '$k=' + filters[i].key;
						if (filters[i].value)
							strfilter += ';v=' + filters[i].value + '$';
					}
					strfilter += '|';
				}
			}
		}
		let strparams = encodeURI("&provider_id=6&param_id=" + this.dataSourceId + "&lon=" + refCoordinates.longitude +
			"&lat=" + refCoordinates.latitude + "&distance=" + this.distanceRadius + "&unit=" + this.distanceUnit + "&filters=" + strfilter);
		searchUrl += strparams;

		const searchHeaders = new Headers();
		const searchRequest = new Request(searchUrl, {
			method: "GET",
			headers: searchHeaders
		});

		let kFlyDuration = 1000; /*ms*/
		// add stores on map as geojson source
		fetch(searchRequest)
			.then(response => response.text())
			.then(data => {
				var jsonData = JSON.parse(data);
				if (jsonData.features.length > 0) {
					this.mapView.addGeoJsonAsCustomMarkers(data, this.controlName, this.sourcesList, this.externalRenderObj);
					// fly to stores bounding box
					if (this.sourcesList.size() > 0) {
						var storesBbox = this.sourcesList.get(0).getArea();
						if (storesBbox.isEmpty == true) {
							let coordsItem = this.sourcesList.get(0).getMarkerAt(0).getCoordinatesPartAt(0).get(0);
							this.mapView.centerOnCoordinates(coordsItem, kFlyDuration);
						} else {
							this.mapView.centerOnArea(storesBbox.leftTop, storesBbox.rightBottom, kFlyDuration);
						}
					}
				} else {
					// no stores found around location
					this.mapView.centerOnCoordinates(refCoordinates, kFlyDuration);
				}
				// list change
				if (this.listChangedNotifier)
					this.listChangedNotifier(this.externalRenderObj.name, this.mapView);
			})
			.catch(e => {
				console.log(e);
			});
	}
}

/** Control for displaying a distance filter applied to a data source control
 * @param {object} filterOptions - distance filter options
 *   @param {string} filterOptions.parentContainer - the parent element id where to add the filter element
 *   @param {string} filterOptions.name - unique name of the filter
 *   @param {string} [filterOptions.title] - title text to display for the filter
 *   @param {string} [filterOptions.eUnit = 'km'] - available options 'km', 'mile'
 *   @param {number[]} [filterOptions.thresholds = [10, 25, 50, 100, 200]] - specify filter distance options
 *   @param {string} [filterOptions.style = 'dropdown']
 *   @param {object} [filterOptions.cssClasses] - customize filter style rules
 *     @param {string} [filterOptions.cssClasses.container]
 *     @param {string} [filterOptions.cssClasses.selectContainer]
 *     @param {string} [filterOptions.cssClasses.titleContainer]
 * @param {gem.control.AddedDataControl} addedDataControl - data source control
 */
gem.control.DistanceFilterControl = class DistanceFilterControl extends gem.control.BaseControl {
	constructor(filterOptions, addedDataControl) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);

		this.addedDataControl = addedDataControl;
		let defaultOptions = {
			parentContainer: '',
			name: '',
			title: '',
			eUnit: 'km',
			thresholds: [10, 25, 50, 100, 200],
			style: 'dropdown',
			cssClasses: {
				container: 'gem-markers-filter-distance',
				selectContainer: 'gem-markers-filter-distance-select',
				titleContainer: 'gem-markers-filter-distance-title'
			}
		}
		this.options = Object.assign({}, defaultOptions, filterOptions);
		this.type = 'distance';
	}

	initControl(parentDiv, mapView) {
		if (!this.addedDataControl.notifyChangeFilterData) {
			console.log("data source control does not support distance filter");
			return;
		}
		if (this.addedDataControl.feedDataOptions && this.addedDataControl.feedDataOptions.populateForMapBrowse) {
			console.log("data source control does not support distance filter");
			return;
		}
		this.mapView = mapView;
		let elFiltersContainer = document.getElementById(this.options.parentContainer);
		if (!elFiltersContainer) {
			elFiltersContainer = document.createElement('div');
			elFiltersContainer.id = this.options.parentContainer;
			elFiltersContainer.className = this.options.parentContainer;
			document.getElementById(parentDiv).appendChild(elFiltersContainer);
		}
		this.createDistanceFilterControl(elFiltersContainer);
	}

	createDistanceFilterControl(parentDiv) {
		if (!this.options.thresholds) {
			console.log("no distance thresholds set for distanceFilter");
			return;
		}

		let elDistance = document.createElement('div');
		elDistance.className = this.options.cssClasses.container;

		// distance radius thresholds
		let distanceThresholds = this.options.thresholds;
		let elSelect = document.createElement('select');
		elSelect.className = this.options.cssClasses.selectContainer;
		for (let i = 0; i < distanceThresholds.length; i++) {
			let distanceOption = document.createElement('option');
			distanceOption.setAttribute('value', distanceThresholds[i]);
			distanceOption.textContent = distanceThresholds[i];
			elSelect.appendChild(distanceOption);
		}
		elDistance.appendChild(elSelect);

		// first value default selected
		this.addedDataControl.notifyChangeFilterData(this.type, this.options.name, distanceThresholds[0]);

		// event listener on change
		this.addedDataControl.distanceUnit = this.options.eUnit;
		elSelect.addEventListener('change', () => {
			this.addedDataControl.notifyChangeFilterData(this.type, this.options.name, elSelect.value);
		});

		// title - optional
		if (this.options.title) {
			let elTitle = document.createElement('div');
			elTitle.className = this.options.cssClasses.titleContainer;
			let spanTitle = document.createElement('span');
			spanTitle.textContent = this.options.title;
			elTitle.appendChild(spanTitle);
			elDistance.insertBefore(elTitle, elDistance.firstChild);
		}
		parentDiv.appendChild(elDistance);
	}
}

/** Control for displaying a categories (properties) filter applied to a data source control
 * @param {object} filterOptions - category filter options
 *   @param {string} filterOptions.parentContainer - the parent element id where to add the filter element
 *   @param {bool} [filterOptions.collapsible = true] - create a collapsible filter
 *   @param {bool} [filterOptions.startCollapsed = true] - show filter categories collapsed at start-up
 *   @param {string} filterOptions.name - unique name of the filter
 *   @param {string} [filterOptions.title] - title text to display for the filter
 *   @param {object[]} filterOptions.categories
 * 	   @param {string} filterOptions.categories.label - label text to display for the filter option
 *     @param {object} filterOptions.categories.filter
 *       @param {string} filterOptions.categories.filter.key - data source item property key to filter
 *       @param {string} filterOptions.categories.filter.value - data source item property value to filter
 * 		 @param {object} filterOptions.categories.children - applicable for 'checkboxes' style 
 *   @param {string} [filterOptions.style = 'radio'] - available options 'radio', 'checkboxes'
 *   @param {object} [filterOptions.cssClasses] - customize filter style rules
 * 	   @param {string} [filterOptions.cssClasses.container]
 *     @param {string} [filterOptions.cssClasses.filterHeader]
 *     @param {string} [filterOptions.cssClasses.collapsible]
 *     @param {string} [filterOptions.cssClasses.inputContainer]
 *     @param {string} [filterOptions.cssClasses.titleContainer]
 * @param {gem.control.AddedDataControl} addedDataControl - data source control
 */
gem.control.CategoryFilterControl = class CategoryFilterControl extends gem.control.BaseControl {
	constructor(filterOptions, addedDataControl) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);

		this.addedDataControl = addedDataControl;

		let defaultOptions = {
			parentContainer: '',
			collapsible: true,
			startCollapsed: true,
			name: '',
			title: '',
			categories: [{
				label: 'All',
				filter: {
					key: '',
					value: ''
				}
			}],
			style: 'radio',
			cssClasses: {
				container: 'gem-markers-filter-categories',
				filterHeader: 'gem-markers-filter-header',
				collapsible: 'gem-markers-filter-collapsible',
				inputContainer: 'gem-markers-filter-input',
				titleContainer: 'gem-markers-filter-categories-title'
			}
		};
		this.options = Object.assign({}, defaultOptions, filterOptions);
		this.type = 'category';
	}

	initControl(parentDiv, mapView) {
		if (!this.addedDataControl || !this.addedDataControl.notifyChangeFilterData) {
			console.log("data source control does not support category filters");
			return;
		}

		this.mapView = mapView;
		this.filterCategory = [ /* {key: '', value: ''} */];

		let elCategoryFilter = document.getElementById(this.options.parentContainer);
		if (!elCategoryFilter) {
			elCategoryFilter = document.createElement('div');
			elCategoryFilter.id = this.options.parentContainer;
			elCategoryFilter.className = this.options.parentContainer;
			document.getElementById(parentDiv).appendChild(elCategoryFilter);
		}
		this.createFilterCategoryControl(elCategoryFilter);
	}

	createFilterCategoryControl(parentDiv) {
		if (!this.options.categories) {
			console.log("no categories were set for categoryFilters");
			return;
		}

		let elCategories = document.createElement('div');
		elCategories.className = this.options.cssClasses.container;
		let elCategoriesFilters = elCategories.appendChild(document.createElement('div'));

		switch (this.options.style) {
			case 'radio':
				this.createRadioCategories(this.options.categories, elCategoriesFilters);
				break;
			case 'checkboxes':
				this.createCheckboxCategories(this.options.categories, elCategoriesFilters);
				break;
			default:
				console.log("filter style not supported");
				break;
		}

		if (this.options.title || this.options.collapsible) {
			let elCategoriesHeader = document.createElement('div');
			elCategoriesHeader.className = this.options.cssClasses.filterHeader;

			if (this.options.collapsible) {
				if (this.options.startCollapsed)
					elCategoriesFilters.style.display = 'none';
				let elCollapse = elCategoriesHeader.appendChild(document.createElement('button'));
				elCollapse.className = this.options.cssClasses.collapsible;
				elCollapse.addEventListener('click', (e) => {
					elCollapse.classList.toggle('active');
					if (elCategoriesFilters.style.display === 'none')
						elCategoriesFilters.style.display = '';
					else
						elCategoriesFilters.style.display = 'none';
				});
				if (!this.options.startCollapsed)
					elCollapse.classList.add('active');
			}

			if (this.options.title) {
				let elTitle = elCategoriesHeader.appendChild(document.createElement('div'));
				elTitle.className = this.options.cssClasses.titleContainer;
				let spanTitle = document.createElement('span');
				spanTitle.innerHTML = this.options.title;
				elTitle.appendChild(spanTitle);
			}
			elCategories.insertBefore(elCategoriesHeader, elCategories.firstChild);
		}
		parentDiv.appendChild(elCategories);
	}

	createRadioCategories(categories, elParent) {
		for (let i = 0; i < categories.length; i++) {
			let elCategory = document.createElement('div');

			let inputCategory = document.createElement('input');
			inputCategory.className = this.options.cssClasses.inputContainer;
			inputCategory.setAttribute('type', 'radio');
			inputCategory.setAttribute('name', this.options.parentContainer);
			inputCategory.setAttribute('value', categories[i].label);
			inputCategory.setAttribute('id', categories[i].label);
			// set first option default checked
			if (i === 0) {
				inputCategory.checked = true;
				this.filterCategory[0] = categories[i].filter;
				this.addedDataControl.notifyChangeFilterData(this.type, this.options.name, this.filterCategory);
			}
			// on click update data source
			inputCategory.addEventListener('click', () => {
				this.filterCategory[0] = categories[i].filter;
				this.addedDataControl.notifyChangeFilterData(this.type, this.options.name, this.filterCategory);
			});
			elCategory.appendChild(inputCategory);

			let labelCategory = document.createElement('label');
			labelCategory.setAttribute('for', categories[i].label);
			labelCategory.innerHTML = categories[i].label;
			elCategory.appendChild(labelCategory);

			elParent.appendChild(elCategory);
		}
	}

	createCheckboxCategories(categories, elParent) {
		for (let i = 0; i < categories.length; i++) {
			let elCategory = elParent.appendChild(document.createElement('div'));
			let inputCategory = elCategory.appendChild(document.createElement('input'));
			inputCategory.className = this.options.cssClasses.inputContainer;
			inputCategory.setAttribute('type', 'checkbox');
			inputCategory.setAttribute('name', this.options.parentContainer);
			inputCategory.setAttribute('value', categories[i].label);
			inputCategory.setAttribute('id', categories[i].label);

			inputCategory.addEventListener('change', () => {
				if (inputCategory.checked) {
					// check all children and add to filter
					if (elCategory.children) {
						let children = elCategory.querySelectorAll('.' + this.options.cssClasses.inputContainer);
						// check without parent input
						if (children.length > 1 && children.length - 1 === categories[i].children.length) {
							for (let c = 1; c < children.length; c++) {
								children[c].checked = true;
								let filter = categories[i].children[c - 1].filter;
								let findFilter = this.filterCategory.find(findFilter => findFilter.key === filter.key && findFilter.value === filter.value);
								if (!findFilter)
									this.filterCategory.push(filter);
							}
						}
					}
					this.addedDataControl.notifyChangeFilterData(this.type, this.options.name, this.filterCategory);
				} else {
					// uncheck all children and remove from filter
					if (elCategory.children) {
						let children = elCategory.querySelectorAll('.' + this.options.cssClasses.inputContainer);
						// check without parent input
						if (children.length > 1 && children.length - 1 === categories[i].children.length) {
							for (let c = 1; c < children.length; c++) {
								children[c].checked = false;
								let childCateg = categories[i].children[c - 1];
								const idxRem = this.filterCategory.indexOf(childCateg.filter);
								if (idxRem > -1)
									this.filterCategory.splice(idxRem, 1);
							}
						}
					}
					this.addedDataControl.notifyChangeFilterData(this.type, this.options.name, this.filterCategory);
				}
			});

			let label = elCategory.appendChild(document.createElement('label'));
			label.setAttribute('for', categories[i].label);
			label.innerHTML = categories[i].label;

			// child checkboxes
			if (categories[i].children) {
				for (let j = 0; j < categories[i].children.length; j++) {
					let childCateg = categories[i].children[j];
					let elChild = elCategory.appendChild(document.createElement('div'));
					let inputChild = elChild.appendChild(document.createElement('input'));
					inputChild.className = this.options.cssClasses.inputContainer;
					inputChild.style.marginLeft = '7%';
					inputChild.setAttribute('type', 'checkbox');
					inputChild.setAttribute('name', this.options.parentContainer);
					inputChild.setAttribute('value', childCateg.label);
					inputChild.setAttribute('id', childCateg.label);

					inputChild.addEventListener('change', () => {
						if (inputChild.checked) {
							// all children checked or set parent indeterminate
							if (elCategory.children && elCategory.children.length > 1) {
								let countChildrenChecked = 0;
								let children = elCategory.querySelectorAll('.' + this.options.cssClasses.inputContainer);
								// check without parent input
								for (let c = 1; c < children.length; c++) {
									if (children[c].checked)
										countChildrenChecked++;
								}
								// set parent input state
								if (countChildrenChecked === children.length - 1) {
									children[0].checked = true;
									children[0].indeterminate = false;
								} else {
									children[0].checked = false;
									children[0].indeterminate = true;
								}
							}
							this.filterCategory.push(childCateg.filter);
						} else {
							// all children unchecked and set parent unchecked or set parent indeterminate
							if (elCategory.children && elCategory.children.length > 1) {
								let countChildrenChecked = 0;
								let children = elCategory.querySelectorAll('.' + this.options.cssClasses.inputContainer);
								// check without parent input
								for (let c = 1; c < children.length; c++) {
									if (children[c].checked) {
										countChildrenChecked++;
										break;
									}
								}
								// set parent input state
								if (countChildrenChecked === 0) {
									children[0].checked = false;
									children[0].indeterminate = false;
								} else {
									children[0].checked = false;
									children[0].indeterminate = true;
								}
							}
							const idxRem = this.filterCategory.indexOf(childCateg.filter);
							if (idxRem > -1)
								this.filterCategory.splice(idxRem, 1);
						}
						this.addedDataControl.notifyChangeFilterData(this.type, this.options.name, this.filterCategory);
					});

					let labelChild = elChild.appendChild(document.createElement('label'));
					labelChild.setAttribute('for', childCateg.label);
					labelChild.innerHTML = childCateg.label;
				}
			}
		}
	}
}

gem.control.DataQuerySearchControl = class DataQuerySearchControl extends gem.control.SearchControl {
	constructor(searchOptions, addedDataControl) {
		super(searchOptions);
		this.addedDataControl = addedDataControl;
	}

	async resultOnClick(pickedResult, searchInputDiv) {
		super.resultOnClick(pickedResult, searchInputDiv);
		if (this.addedDataControl) {
			this.addedDataControl.refLocation = pickedResult;
			this.addedDataControl.centerPoint = pickedResult.getCoordinates();

			if (this.addedDataControl.updateDataSource)
				this.addedDataControl.updateDataSource();
			else if (this.addedDataControl.feedDataOptions && this.addedDataControl.feedDataOptions.markersFeedFunction) {
				let mapCanvas = document.getElementById(gem.core.App.initOptions.container);
				if (mapCanvas) {
					let mapScreenTopLeft = {
						x: mapCanvas.clientLeft,
						y: mapCanvas.clientTop
					};
					let mapScreenBottomRight = {
						x: mapCanvas.clientLeft + mapCanvas.clientWidth,
						y: mapCanvas.clientTop + mapCanvas.clientHeight
					}
					let mapWgsTopLeft = this.addedDataControl.mapView.transformScreenToWgs(mapScreenTopLeft);
					let mapWgsBottomRight = this.addedDataControl.mapView.transformScreenToWgs(mapScreenBottomRight);
					let mapWgsLonMin = mapWgsTopLeft.longitude;
					let mapWgsLonMax = mapWgsBottomRight.longitude;
					let mapWgsLatMin = Math.min(mapWgsTopLeft.latitude, mapWgsBottomRight.latitude);
					let mapWgsLatMax = Math.max(mapWgsTopLeft.latitude, mapWgsBottomRight.latitude);

					let data = await this.addedDataControl.feedDataOptions.markersFeedFunction(pickedResult.longitude, pickedResult.latitude,
						mapWgsLonMin, mapWgsLatMin, mapWgsLonMax, mapWgsLatMax);
					try {
						this.addedDataControl.mapView.addGeoJsonAsCustomMarkers(JSON.stringify(data), this.addedDataControl.controlName, this.addedDataControl.sourcesList, this.addedDataControl.externalRenderObj, this.addedDataControl.options.markerGrouping.maxLevel);
						if (!data || !data.features || !data.features.length) {
							if (this.listChangedNotifier)
								this.listChangedNotifier(this.externalRenderObj.name, this.mapView);
							if (this.listLoadingDoneNotifier)
								this.listLoadingDoneNotifier();
						}
					} catch (e) {
						console.log("CustomQueryAddedDataControl error: invalid data for markers");
					}
				}
			} else
				return;
		}
	}

}

class FiltersControl extends gem.control.BaseControl {
	constructor(initFiltersOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);
		this.datacontrol = initFiltersOptions.datacontrol;
		if (this.datacontrol)
			this.datacontrol.registerVisibilityNotifier(this.visibilityReceiver.bind(this));
		if (initFiltersOptions.container)
			this.container = initFiltersOptions.container;
		// distance  
		if (initFiltersOptions.filterKey) {
			this.filterKey = initFiltersOptions.filterKey;
			this.filterValues = initFiltersOptions.filterValues;
			if (this.filterValues && this.filterValues.length > 0)
				this.selectedFilterValue = initFiltersOptions.filterValues[0];
		}
		this.title = initFiltersOptions.title;
	};
	visibilityReceiver(visibility) {
		if (this.elContainer)
			this.elContainer.style.visibility = visibility;
		else
			this.visibility = visibility;
	}
	initControl(parentDiv, mapView) {
		this.mapView = mapView;
		if (this.container) {
			let elContainer = document.getElementById(this.container);
			if (this.visibility)
				elContainer.style.visibility = this.visibility;
			this.elContainer = elContainer;
			if (this.filterKey)
				this.createDistanceFilterControl(elContainer);
		}
		// create default container
		else {
			let elFiltersContainer = document.createElement('div');
			this.elContainer = elFiltersContainer;
			if (this.visibility)
				this.elContainer.style.visibility = this.visibility;
			elFiltersContainer.className = 'gem-markers-filter-container';
			if (this.filterKey) {
				if (this.filterValues)
					this.createDistanceFilterControl(elFiltersContainer);
				else {
					this.filterContainer = elFiltersContainer;
					this.datacontrol.requestFieldValues(this.filterKey, this.populateFilterControl.bind(this));
				}
			}
			let lstchild = document.getElementById(parentDiv).lastChild;
			if (lstchild && lstchild.id !== 'buttonCopyright')
				elFiltersContainer.style.top = lstchild.offsetTop + lstchild.offsetHeight + 2 + 'px';
			document.getElementById(parentDiv).appendChild(elFiltersContainer);
		}
	};
	populateFilterControl(itemsList) {
		if (itemsList.size() === 0)
			return;
		let elDistance = document.createElement('div');
		elDistance.id = 'gem-markers-filter-distance';
		elDistance.className = 'gem-markers-filter-distance';
		let elSelect = document.createElement('select');
		elSelect.className = 'gem-markers-filter-distance-select';
		for (let i = 0; i < itemsList.size(); i++) {
			let distanceOption = document.createElement('option');
			distanceOption.setAttribute('value', i + "");
			distanceOption.innerHTML = itemsList.get(i).getFormattedText();
			elSelect.appendChild(distanceOption);
		}
		elDistance.appendChild(elSelect);
		let self = this;
		elSelect.addEventListener('change', function () {
			self.datacontrol.notifyChangeFilterData(self.filterKey, 'distanceFilter', elSelect.value);
		});
		if (this.title) {
			let elTitle = document.createElement('div');
			elTitle.id = 'gem-markers-filter-distance-title';
			elTitle.className = 'gem-markers-filter-distance-title';
			let spanTitle = document.createElement('span');
			spanTitle.innerHTML = this.title;
			elTitle.appendChild(spanTitle);
			elDistance.insertBefore(elTitle, elDistance.firstChild);
		}
		this.filterContainer.appendChild(elDistance);

	}
	createDistanceFilterControl(parentDiv) {
		if (!this.filterValues) {
			console.log("no distance thresholds set for distanceFilter");
			return;
		}

		let elDistance = document.createElement('div');
		elDistance.id = 'gem-markers-filter-distance';
		elDistance.className = 'gem-markers-filter-distance';

		// distance radius thresholds
		let distanceThresholds = this.filterValues;
		let elSelect = document.createElement('select');
		elSelect.className = 'gem-markers-filter-distance-select';
		for (let i = 0; i < distanceThresholds.length; i++) {
			let distanceOption = document.createElement('option');
			distanceOption.setAttribute('value', distanceThresholds[i]);
			distanceOption.innerHTML = distanceThresholds[i];
			elSelect.appendChild(distanceOption);
		}
		elDistance.appendChild(elSelect);
		let self = this;
		elSelect.addEventListener('change', function () {
			self.datacontrol.notifyChangeFilterData(self.filterKey, 'distanceFilter', elSelect.value);
		});
		if (this.title) {
			let elTitle = document.createElement('div');
			elTitle.id = 'gem-markers-filter-distance-title';
			elTitle.className = 'gem-markers-filter-distance-title';
			let spanTitle = document.createElement('span');
			spanTitle.innerHTML = this.title;
			elTitle.appendChild(spanTitle);
			elDistance.insertBefore(elTitle, elDistance.firstChild);
		}
		parentDiv.appendChild(elDistance);
	}
}


gem.control.WikipediaControl = class WikipediaControl extends gem.control.BaseControl {

	constructor(initFiltersOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);
		if (initFiltersOptions) {
			if (initFiltersOptions.container)
				this.container = initFiltersOptions.container;
			if (initFiltersOptions.populateItemFunction)
				this.populateItemFunction = initFiltersOptions.populateItemFunction;
		}
	}
	initControl(parentDiv, mapView) {
		this.mapView = mapView;
		if (this.container) {
			let elContainer = document.getElementById(this.container);
			if (this.visibility)
				elContainer.style.visibility = this.visibility;
			this.elContainer = elContainer;
		} else {
			let wikipediaContainer = document.createElement('div');
			wikipediaContainer.className = 'gem-markers-filter-container';
			this.elContainer = wikipediaContainer;
			if (this.visibility)
				this.elContainer.style.visibility = this.visibility;
			let lstchild = document.getElementById(parentDiv).lastChild;
			if (lstchild && lstchild.id !== 'buttonCopyright')
				wikipediaContainer.style.top = lstchild.offsetTop + lstchild.offsetHeight + 2 + 'px';
			document.getElementById(parentDiv).appendChild(wikipediaContainer);
		}
	}
	cameraChangedCallback() {

	}
	preRenderCallback() {

	}
	setControlMode(mode) {

	}
	notifySearchSelection(item) {

		while (this.elContainer.firstChild) {
			this.elContainer.firstChild.remove();
		}
		if (!this.pExternalInfo)
			this.pExternalInfo = new gem.core.ExternalInfo();
		if (item) {
			if (item.hasWikipediaInfo(this.pExternalInfo)) {
				item.requestWikipediaInfo(this.wikipediainfocallback.bind(this), this.pExternalInfo);
			} else {
				this.elContainer.style.visibility = 'hidden';
			}
		} else {
			this.elContainer.innerHTML = "";
			this.elContainer.style.visibility = 'hidden';
		}
	}
	wikipediainfocallback(wikiResult) {
		if (this.populateItemFunction) {
			this.elContainer.style.visibility = 'visible';
			this.populateItemFunction(this.elContainer, wikiResult);
		} else {
			let pageDescription = document.createElement('div');
			pageDescription.className = 'gem-wikipedia-description';
			pageDescription.innerHTML = "<p>" + wikiResult.getWikiPageDescription() + "</p>";
			this.elContainer.style.visibility = 'visible';
			if (wikiResult.getWikiImagesUrl().size())
				this.elContainer.innerHTML = '<img src="' + wikiResult.getWikiImagesUrl().get(0) + '" alt="Snow" style="width:100%">';
			this.elContainer.appendChild(pageDescription);
			let pageImages = document.createElement('div');
			pageImages.className = 'gem-wikipedia-container';
			let whatToAdd = '<div class="row">';
			for (let i = 1; i < wikiResult.getWikiImagesUrl().size(); i++) {
				whatToAdd += '<div class="column"><img src="' + wikiResult.getWikiImagesUrl().get(i) + '"loading="lazy" style="width:100%"></div>';
			}
			whatToAdd += '</div>';
			pageImages.innerHTML = whatToAdd;
			this.elContainer.appendChild(pageImages);
		}
	}

	visibilityReceiver(visibility) {
		if (this.elContainer)
			this.elContainer.style.visibility = visibility;
		else
			this.visibility = visibility;
	}
}

gem.control.ContentStoreControl = class ContentStoreControl extends gem.control.BaseControl {
	constructor(initOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);
		if (initOptions) {
			this.contentType = initOptions.contentType;
			this.customContainer = initOptions.container;
			this.createElementFunction = initOptions.createElementFunction;
		}

	}

	initControl(parentDiv, mapView) {
		this.elementDiv = document.createElement("div");
		this.elementDiv.classList.add("gem-markers-menu-list");
		let containerDiv = document.getElementById(this.customContainer ? this.customContainer : parentDiv);
		let searchControlsDiv = document.createElement('div');
		searchControlsDiv.id = "searchIn";
		searchControlsDiv.className = "gem-search-input";


		let searchControlsDivParent = document.createElement('div');
		searchControlsDivParent.id = "searchControl";
		searchControlsDivParent.className = "gem-search-control";
		searchControlsDivParent.appendChild(searchControlsDiv);
		containerDiv.appendChild(this.elementDiv);
		containerDiv.appendChild(searchControlsDivParent);
		this.requestContentList();
	}
	requestContentList() {
		this.mStoreProgressListener = new gem.content.StoreProgressListener();
		this.mStoreProgressListener.registerCompleteCallback(this.completeCallback.bind(this));
		gem.content.Manager.requestContentList((typeof this.contentType === 'function') ? this.contentType() : this.contentType, this.mStoreProgressListener);
	}
	completeCallback() {
		this.contentStoreList = gem.content.Manager.getStoreContentList((typeof this.contentType === 'function') ? this.contentType() : this.contentType);
		this.resetList();
	}
	preRenderCallback() {
		if (this.notifySaveDataToDisk) {
			gem.core.App.syncContentWithStorage(false);
			this.notifySaveDataToDisk = false;
		}
	}
	populateElement(pElement, categoryItem) {
		while (pElement.firstChild) {
			pElement.firstChild.remove();
		}
		let bitmapSize = 20;
		const offbitmap = new gem.core.BitmapContainer(bitmapSize, bitmapSize);
		let countryCodes = categoryItem.getCountryCodes();
		gem.core.App.getCountryFlagImage(countryCodes.get(0), offbitmap);
		let imgCanvas = document.createElement('canvas');
		imgCanvas.setAttribute('width', '' + bitmapSize);
		imgCanvas.setAttribute('height', '' + bitmapSize);
		imgCanvas.style.backgroundColor = 'transparent';
		gem.core.App.setCanvas(offbitmap.toImageData(), bitmapSize, bitmapSize, imgCanvas);
		offbitmap.delete();
		let pElementCanvas = document.createElement("div");
		pElementCanvas.setAttribute('width', '' + bitmapSize);
		pElementCanvas.setAttribute('height', '' + bitmapSize);
		pElementCanvas.appendChild(imgCanvas);

		let pElementTitle = document.createElement("div");
		pElementTitle.className = 'gem-marker-list-item-title';
		pElementTitle.style.cursor = 'pointer';
		let textPart = document.createElement('SPAN');
		textPart.innerText = categoryItem.getName();
		pElementTitle.appendChild(textPart);
		pElementCanvas.appendChild(pElementTitle);
		pElement.appendChild(pElementCanvas);
		let pElementContent = document.createElement("div");

		if (!categoryItem.isCompleted()) {
			pElement.onclick = function () {
				console.log("Check category test");
				let progressDiv = document.createElement("DIV");

				let progressElement = document.createElement("progress");
				progressElement.setAttribute("value", "0");
				progressElement.setAttribute("max", "100");
				progressDiv.style.lineHeight = 'line-height:normal'
				progressDiv.style.display = 'inline-block';
				progressDiv.style.verticalAlign = 'bottom';
				progressDiv.appendChild(progressElement);
				pElement.appendChild(progressDiv);
				this.mStoreProgressListener = new gem.content.StoreProgressListener();
				let functionCompleted = function () {
					console.log("Completed The download !");
					progressElement.remove();
					this.populateElement(pElement, categoryItem);
					this.notifySaveDataToDisk = true;
				}.bind(this);
				let functionProgressCallback = function (val) {
					progressElement.setAttribute("value", val + "");
				}.bind(this);

				Module.m_progressListenerHTTP = functionProgressCallback;
				this.mStoreProgressListener.registerCompleteCallback(functionCompleted);
				this.mStoreProgressListener.registerProgressCallback(functionProgressCallback);
				categoryItem.download(this.mStoreProgressListener);
			}.bind(this);
		} else {
			console.log("Item " + categoryItem.getName() + " is downloaded!");
			let textPart2 = document.createElement("BUTTON");
			textPart2.innerText = "delete";
			textPart2.onclick = function (event) {
				categoryItem.deleteContent();
				pElement.removeChild(textPart2);
				this.populateElement(pElement, categoryItem);
				event.stopPropagation();
				this.notifySaveDataToDisk = true;
			}.bind(this);
			pElement.appendChild(textPart2);
			if (categoryItem.isUpdatable()) {
				let updateBt = document.createElement("BUTTON");
				updateBt.innerText = "update";
			}
		}

	}
	resetList() {
		while (this.elementDiv.firstChild) {
			this.elementDiv.firstChild.remove();
		}
		let i = 0;
		for (i = 0; i < this.contentStoreList.size(); i++) {
			let categoryItem = this.contentStoreList.get(i);
			let pElement = document.createElement("div");
			pElement.className = 'gem-marker-list-item';
			pElement.style.cursor = 'pointer';
			pElement.style.flexDirection = 'column';

			this.populateElement(pElement, categoryItem);
			this.elementDiv.appendChild(pElement);
			//var bitmapContainer = new gem.core.BitmapContainer(20, 20);
			//let poiIcon = document.createElement("CANVAS");
			//poiIcon.style = "margin-top: auto;margin-bottom: auto;";
			//categoryItem.getImage(bitmapContainer);
			//gem.core.App.setCanvas(bitmapContainer.toImageData(), 20, 20, poiIcon);
			//pElement.appendChild(poiIcon);
			//bitmapContainer.delete();
			;

		}

	}
}
gem.control.TouchEventControl = class TouchEventControl extends gem.control.BaseControl {
	constructor(initOptions) {
		super(gem.control.TConntrolExecuteType.ONCONNECTED);
		if (initOptions) {
			this.onTapEvent = initOptions.tapEvent;
			this.onLongTapEvent = initOptions.longTapEvent;
		}
	}

	initControl(parentDiv, mapView) {
		if (this.onTapEvent)
			mapView.registerOnTouchEvent(this.onTapEvent);
		if (this.onLongTapEvent)
			mapView.registernOnLongTouchEvent(this.onLongTapEvent);
	}
}
/**
 * @class gem.d3Scene.MarkerCollectionDisplaySettings
 * @description A class that represents the display settings for a marker collection.
 *
 * This class provides methods to manage the display settings of a marker collection.
 *
 * @description Initializes a new instance of the `gemMarkerCollectionDisplaySettings` class.
 *
 * @function setPolylineInnerColor
 * @description Sets the inner color of the polyline.
 * @param {object} color - The color to set in RGBA format.
 *
 * @function setPolylineOuterColor
 * @description Sets the outer color of the polyline.
 * @param {object} color - The color to set in RGBA format.
 *
 * @function setPolygonFillColor
 * @description Sets the fill color of the polygon.
 * @param {object} color - The color to set in RGBA format.
 *
 * @function setPolylineInnerSize
 * @description Sets the inner size of the polyline.
 * @param {number} innerSize - The size to set.
 *
 * @function setPolylineOuterSize
 * @description Sets the outer size of the polyline.
 * @param {number} outerSize - The size to set.
 *
 * @function getRawPointer
 * @description Gets the raw pointer to the `Module.gemMarkerCollectionDisplaySettings` object.
 * @returns {Module.gemMarkerCollectionDisplaySettings} The raw pointer.
 *
 * @example
 * ```javascript
 * let displaySettings = new gem.d3Scene.MarkerCollectionDisplaySettings();
 * displaySettings.setPolylineInnerColor({r: 255, g: 0, b: 0, a: 255});
 * displaySettings.setPolylineOuterColor({r: 0, g: 255, b: 0, a: 255});
 * displaySettings.setPolygonFillColor({r: 0, g: 0, b: 255, a: 255});
 * displaySettings.setPolylineInnerSize(5);
 * displaySettings.setPolylineOuterSize(10);
 * console.log(displaySettings.getRawPointer()); // Outputs: Module.gemMarkerCollectionDisplaySettings
 * ```
 */
gem.d3Scene.MarkerCollectionDisplaySettings = class gemMarkerCollectionDisplaySettings {
	constructor() {
		this.m_object = new Module.gemMarkerCollectionDisplaySettings();
	}
	setPolylineInnerColor(color) {
		this.m_object.setPolylineInnerColor(color);
	}
	setPolylineOuterColor(color) {
		this.m_object.setPolylineOuterColor(color);
	}
	setPolygonFillColor(color) {
		this.m_object.setPolygonFillColor(color);
	}
	setPolylineInnerSize(innerSize) {
		this.m_object.setPolylineInnerSize(innerSize);
	}
	setPolylineOuterSize(outerSize) {
		this.m_object.setPolylineOuterSize(outerSize);
	}
	getRawPointer() {
		return this.m_object;
	}

}
/**
 * @description A constant that represents different types of Electric Vehicle (EV) charging connectors.
 *
 * This constant is an object where each property represents a unique type of EV charging connector.
 *
 * @property {number} EVC_J1772 - Represents the J1772 type of EV charging connector.
 * @property {number} EVC_Mennekes - Represents the Mennekes type of EV charging connector.
 * @property {number} EVC_Type2 - Represents the Type 2 type of EV charging connector.
 * @property {number} EVC_Type3 - Represents the Type 3 type of EV charging connector.
 * @property {number} EVC_GBT_AC - Represents the GBT AC type of EV charging connector.
 * @property {number} EVC_GBT_DC - Represents the GBT DC type of EV charging connector.
 * @property {number} EVC_CHAdeMO - Represents the CHAdeMO type of EV charging connector.
 * @property {number} EVC_CSS1 - Represents the CSS1 type of EV charging connector.
 * @property {number} EVC_CSS2 - Represents the CSS2 type of EV charging connector.
 * @property {number} EVC_Tesla - Represents the Tesla type of EV charging connector.
 * @property {number} EVC_SuperTesla - Represents the SuperTesla type of EV charging connector.
 * @property {number} EVC_SuperTeslaCCS - Represents the SuperTesla CCS type of EV charging connector.
 * @property {number} EVC_TeslaDestination - Represents the Tesla Destination type of EV charging connector.
 *
 * @example
 * ```javascript
 * console.log(EEVChargingConnector.EVC_J1772); // Outputs: 0x1
 * console.log(EEVChargingConnector.EVC_Mennekes); // Outputs: 0x2
 * console.log(EEVChargingConnector.EVC_Type2); // Outputs: 0x4
 * // ...
 * ```
 */
const EEVChargingConnector = {
	EVC_J1772: 0x1,
	EVC_Mennekes: 0x2,
	EVC_Type2: 0x4,
	EVC_Type3: 0x8,
	EVC_GBT_AC: 0x10,
	EVC_GBT_DC: 0x20,
	EVC_CHAdeMO: 0x40,
	EVC_CSS1: 0x80,
	EVC_CSS2: 0x100,
	EVC_Tesla: 0x200,
	EVC_SuperTesla: 0x400,
	EVC_SuperTeslaCCS: 0x800,
	EVC_TeslaDestination: 0x1000
};
/**
 * @class gem.routesAndNavigation.EVCarModel
 * @augments Em_Object
 * @description A class that represents a model of an Electric Vehicle (EV).
 *
 * This class provides properties and methods to manage EV car models.
 *
 * @param {Module.EVCarModel|object} obj - If the argument is an instance of `Module.EVCarModel`, it will be used to initialize the object. Otherwise, a new `Module.EVCarModel` object will be created.
 *
 * @property {number} id - The ID of the EV car model.
 * @property {string} name - The name of the EV car model.
 * @property {number} batteryCapacity - The battery capacity of the EV car model.
 * @property {boolean} towbarPossible - Indicates whether the EV car model can have a towbar.
 * @property {Array} ports - The ports of the EV car model.
 * @property {number} vehicleRange - The range of the EV car model.
 * @property {number} efficiency - The efficiency of the EV car model.
 * @property {boolean} fastCharge - Indicates whether the EV car model supports fast charging.
 *
 * @example
 * ```javascript
 * let evCarModel = new gem.routesAndNavigation.EVCarModel({id: 1, name: 'Model S', batteryCapacity: 100, towbarPossible: true, ports: [], vehicleRange: 500, efficiency: 0.85, fastCharge: true});
 * console.log(evCarModel.name); // Outputs: Model S
 * console.log(evCarModel.batteryCapacity); // Outputs: 100
 * console.log(evCarModel.towbarPossible); // Outputs: true
 * console.log(evCarModel.vehicleRange); // Outputs: 500
 * console.log(evCarModel.efficiency); // Outputs: 0.85
 * console.log(evCarModel.fastCharge); // Outputs: true
 * ```
 */
gem.routesAndNavigation.EVCarModel = class EVCarModel extends Em_Object {
	constructor(obj) {
		super(obj ? obj : new Module.EVCarModel());
	}

	// Properties from EVCarModel
	get id() {
		return this.m_object.id;
	}

	set id(value) {
		this.m_object.id = value;
	}

	get name() {
		return this.m_object.name.toStdString(0, -1);
	}

	set name(value) {
		this.m_object.name = new Module.String(value);
	}

	get batteryCapacity() {
		return this.m_object.batteryCapacity;
	}

	set batteryCapacity(value) {
		this.m_object.batteryCapacity = value;
	}

	get towbarPossible() {
		return this.m_object.towbarPossible;
	}

	set towbarPossible(value) {
		this.m_object.towbarPossible = value;
	}

	get ports() {
		return this.m_object.ports;
	}

	set ports(value) {
		this.m_object.ports = value;
	}

	get vehicleRange() {
		return this.m_object.vehicleRange;
	}

	set vehicleRange(value) {
		this.m_object.vehicleRange = value;
	}

	get efficiency() {
		return this.m_object.efficiency;
	}

	set efficiency(value) {
		this.m_object.efficiency = value;
	}

	get fastCharge() {
		return this.m_object.fastCharge;
	}

	set fastCharge(value) {
		this.m_object.fastCharge = value;
	}
};
/**
 * @class gem.routesAndNavigation.EVProfile
 * @description A class that represents a profile for an Electric Vehicle (EV).
 *
 * This class provides properties and methods to manage EV profiles. It extends the `EVCarModel` class with additional properties and methods.
 *
 * @param {Module.EVProfile|gem.routesAndNavigation.EVCarModel} objOrCarModel - If the first argument is an instance of `Module.EVProfile`, it will be used to initialize the object. Otherwise, the argument is used as the car model to initialize a new `Module.EVProfile` object.
 *
 * @property {gem.routesAndNavigation.EVCarModel} carModel - The car model of the EV.
 * @property {number} batteryCapacity - The battery capacity of the EV.
 * @property {number} chargeSpeed - The charge speed of the EV.
 *
 * @example
 * ```javascript
 * let evProfile = new gem.routesAndNavigation.EVProfile(gem.routesAndNavigation.EVCarModel, 100, 50);
 * console.log(evProfile.carModel); // Outputs: EVCarModel
 * console.log(evProfile.batteryCapacity); // Outputs: 100
 * console.log(evProfile.chargeSpeed); // Outputs: 50
 * ```
 */
gem.routesAndNavigation.EVProfile = class EVProfile extends gem.routesAndNavigation.EVCarModel {
	constructor(obj) {
		if (obj === undefined)
			super(new Module.EVProfile);
		else
			super(obj);

		self = this;

		// Expose properties of EVProfile
		Object.defineProperty(this, 'departureSoc', {
			get: function () {
				return this.m_object.departureSoc;
			},
			set: function (value) {
				this.m_object.departureSoc = value;
			}
		});

		Object.defineProperty(this, 'destinationSoc', {
			get: function () {
				return this.m_object.destinationSoc;
			},
			set: function (value) {
				this.m_object.destinationSoc = value;
			}
		});

		Object.defineProperty(this, 'chargerDepSoc', {
			get: function () {
				return this.m_object.chargerDepSoc;
			},
			set: function (value) {
				this.m_object.chargerDepSoc = value;
			}
		});

		Object.defineProperty(this, 'chargerDestSoc', {
			get: function () {
				return this.m_object.chargerDestSoc;
			},
			set: function (value) {
				this.m_object.chargerDestSoc = value;
			}
		});

		Object.defineProperty(this, 'chargerOverheadMins', {
			get: function () {
				return this.m_object.chargerOverheadMins;
			},
			set: function (value) {
				this.m_object.chargerOverheadMins = value;
			}
		});

		Object.defineProperty(this, 'batteryHealth', {
			get: function () {
				return this.m_object.batteryHealth;
			},
			set: function (value) {
				this.m_object.batteryHealth = value;
			}
		});
	}

	setCarModel(carModel) {
		this.m_object.setCarModel(carModel.getRawPointer());
	}
};
/**
 * @class gem.routesAndNavigation.EFuelType
 * @description
 *
 * This class provides static methods that return different types of fuel. Each method represents a unique fuel type.
 *
 * @example
 * ```javascript
 * let fuelType = gem.routesAndNavigation.EFuelType.FT_Petrol;
 * console.log(fuelType); // Outputs: FT_Petrol
 * ```
 */
gem.routesAndNavigation.EFuelType = class EFuelType {
	static FT_Petrol() {
		return EFuelType.FT_Petrol;
	}

	static FT_Diesel() {
		return EFuelType.FT_Diesel;
	}

	static FT_LPG() {
		return EFuelType.FT_LPG;
	}

	static FT_Electric() {
		return EFuelType.FT_Electric;
	}
	/**
	 * @private
	 */
	static initializeData() {
		EFuelType.FT_Petrol = Module.EFuelType.FT_Petrol;
		EFuelType.FT_Diesel = Module.EFuelType.FT_Diesel;
		EFuelType.FT_LPG = Module.EFuelType.FT_LPG;
		EFuelType.FT_Electric = Module.EFuelType.FT_Electric;
	}
}
/**
 * @class gem.routesAndNavigation.CarProfile
 * @augments Em_Object
 * @description A class that represents a profile for a car, including its fuel type, mass, and maximum speed.
 *
 * @param {Module.CarProfile|gem.routesAndNavigation.EFuelType} objOrFuel - If the first argument is an instance of `Module.CarProfile`, it will be used to initialize the object. Otherwise, the argument is used as the fuel type to initialize a new `Module.CarProfile` object.
 * @param {number} mass - The mass of the car.
 * @param {number} maxSpeed - The maximum speed of the car.
 *
 * @property {gem.routesAndNavigation.EFuelType} fuel - The fuel type of the car.
 * @property {number} mass - The mass of the car.
 * @property {number} maxSpeed - The maximum speed of the car.
 *
 * @example
 * ```javascript
 * let carProfile = new gem.routesAndNavigation.CarProfile(gem.routesAndNavigation.EFuelType.FT_Petrol, 1500, 200);
 * console.log(carProfile.fuel); // Outputs: FT_Petrol
 * console.log(carProfile.mass); // Outputs: 1500
 * console.log(carProfile.maxSpeed); // Outputs: 200
 * ```
 */
gem.routesAndNavigation.CarProfile = class CarProfile extends Em_Object {
	constructor(objOrFuel, mass, maxSpeed) {
		if (objOrFuel && objOrFuel instanceof Module.CarProfile) {
			super(objOrFuel);
		}
		else {
			if (objOrFuel === undefined) {
				objOrFuel = gem.routesAndNavigation.EFuelType.FT_Petrol;
			}
			if (mass === undefined) {
				mass = 0.0;
			}
			if (maxSpeed === undefined) {
				maxSpeed = 0.0;
			}
			super(new Module.CarProfile(objOrFuel, mass, maxSpeed));
		}
		// Expose properties of CarProfile
		Object.defineProperty(this, 'fuel', {
			get: function () {
				return this.m_object.fuel;
			},
			set: function (value) {
				this.m_object.fuel = value;
			}
		});

		Object.defineProperty(this, 'mass', {
			get: function () {
				return this.m_object.mass;
			},
			set: function (value) {
				this.m_object.mass = value;
			}
		});

		Object.defineProperty(this, 'maxSpeed', {
			get: function () {
				return this.m_object.maxSpeed;
			},
			set: function (value) {
				this.m_object.maxSpeed = value;
			}
		});
	}
}
/**
 * @class gem.routesAndNavigation.EVCarModelsList
 * @augments Em_Vector
 * @description A class that represents a list of Electric Vehicle (EV) car models.
 *
 * @function get
 * @param {number} position - The position (index) of the `EVCarModel` object in the list.
 * @returns {gem.routesAndNavigation.EVCarModel} The `EVCarModel` object at the specified position in the list.
 *
 * @example
 * ```javascript
 * let evCarModelsList = new gem.routesAndNavigation.EVCarModelsList();
 * let carModel = evCarModelsList.get(0); // Get the first EVCarModel in the list
 * ```
 */
gem.routesAndNavigation.EVCarModelsList = class EVCarModelsList extends Em_Vector {
	/** This method takes a position (index) as an argument and returns the `EVCarModel` object at that position in the list.
	 * @param {number} position - The position (index) of the `EVCarModel` object in the list.
	 * @returns {gem.routesAndNavigation.EVCarModel} The `EVCarModel` object at the specified position in the list.
	 **/
	get(position) {
		return new gem.routesAndNavigation.EVCarModel(this.m_object.get(position));
	}
}
/**
 * Represents an electric vehicle route instruction.
 * @class gem.routesAndNavigation.EVRouteInstruction
 * @memberof gem.routesAndNavigation
 * @augments gem.core.Em_Object
 * @hideconstructor
 */
gem.routesAndNavigation.EVRouteInstruction = class EVRouteInstruction extends Em_Object {
	/**
	* Get the begin state of charge
	* @returns {number} the begin state of charge
	**/
	getBeginSoC() {
		return this.m_object.getBeginSoC();
	}
	/**
	* Get the end state of charge
	* @returns {number} the end state of charge
	**/
	getEndSoC() {
		return this.m_object.getEndSoC();
	}
	/** 
	* Get the charging time
	* @returns {number} the charging time
	**/
	getChargingTime() {
		return this.m_object.getChargingTime();
	}
	/**
	* Check if stop is a charger
	* @returns {boolean} true if stop is a charger
	**/
	isChargeStop() {
		return this.m_object.isChargeStop();
	}
}

gem.control.SingleMarkerController = class SingleMarkerController extends gem.control.GeoJsonAddedDataControl {
	constructor(coordinates,propertiesJson,iconLocation)
	{
		let geoJsonObject = {
			"type": "FeatureCollection", "features": [
		
				{ "type": "Feature", "id": 1, "geometry": { "type": "Point", "coordinates": [coordinates.latitude,coordinates.longitude] }, "properties": propertiesJson }
		
			]
		};
		super(geoJsonObject,iconLocation,"",undefined);
	}
	moveTo(coordinates){
		if(this.tvectorSources.size()==1)
			{
				let markerColl = this.tvectorSources.get(0);
				if(markerColl.size() == 1)
				{
					let marker = markerColl.getMarkerAt(0);
					marker.movePoint(0,coordinates);
				}
			}
	}

}