I have a map built with leaflet. There are three basemaps. The first one is a basic OSM map. The second and third one ('Boundaries' and 'FNP') are WMS. I want to show attributes by GetFeatureInfo from the WMS 'FNP', but just want to request the values of the columns 'GEMEINDE', 'NUTZUNG' and 'STAND'. Here is the code for this:
<!DOCTYPE html>
<html lang="de">
<head>
<title>FNP</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<link rel="shortcut icon" type="image/x-icon" href="docs/images/favicon.ico" />
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.3.3/dist/leaflet.css" integrity="sha512-Rksm5RenBEKSKFjgI3a41vrjkw4EVPlJ3+OiI65vTjIdo9brlAacEuKOiQ5OFh7cOI1bkDwLqdLw3Zg0cRJAAQ==" crossorigin=""/>
<script src="https://unpkg.com/leaflet#1.3.3/dist/leaflet.js" integrity="sha512-tAGcCfR4Sc5ZP5ZoVz0quoZDYX5aCtEm/eu1KhSLj2c9eFrylXZknQYmxUssFaVJKvvc0dJQixhGjG2yXWiV9Q==" crossorigin=""></script>
<script src="/cdn-cgi/apps/head/WCXTfKrGxLNzfpUe-D2TgHwMpm4.js"></script><link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<style>
html, body {
height: 100%
}
#mapid {
width: 1000px;
height: 800px;
}
#media (max-width: 1000px) {
#mapid{
width: 100%;
height: 100%
}
}
</style>
</head>
<body>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="L.TileLayer.BetterWMS.js"></script>
<div id="mapid"></div>
<script type="text/javascript">
<!-- *********************** betterWms ************************************ -->
L.TileLayer.BetterWMS = L.TileLayer.WMS.extend({
onAdd: function (map) {
// Triggered when the layer is added to a map.
// Register a click listener, then do all the upstream WMS things
L.TileLayer.WMS.prototype.onAdd.call(this, map);
map.on('click', this.getFeatureInfo, this);
},
onRemove: function (map) {
// Triggered when the layer is removed from a map.
// Unregister a click listener, then do all the upstream WMS things
L.TileLayer.WMS.prototype.onRemove.call(this, map);
map.off('click', this.getFeatureInfo, this);
},
getFeatureInfo: function (evt) {
// Make an AJAX request to the server and hope for the best
var url = this.getFeatureInfoUrl(evt.latlng),
showResults = L.Util.bind(this.showGetFeatureInfo, this);
$.ajax({
url: url,
success: function (data, status, xhr) {
var err = typeof data === 'string' ? null : data;
showResults(err, evt.latlng, data);
},
error: function (xhr, status, error) {
showResults(error);
}
});
},
getFeatureInfoUrl: function (latlng) {
// Construct a GetFeatureInfo request URL given a point
var point = this._map.latLngToContainerPoint(latlng, this._map.getZoom()),
size = this._map.getSize(),
params = {
request: 'GetFeatureInfo',
service: 'WMS',
srs: 'EPSG:4326',
styles: this.wmsParams.styles,
transparent: this.wmsParams.transparent,
version: this.wmsParams.version,
format: this.wmsParams.format,
bbox: this._map.getBounds().toBBoxString(),
height: size.y,
width: size.x,
layers: this.wmsParams.layers,
query_layers: this.wmsParams.layers,
info_format: 'text/html',
propertyName: 'GEMEINDE,NUTZUNG,STAND'
};
params[params.version === '1.3.0' ? 'i' : 'x'] = point.x;
params[params.version === '1.3.0' ? 'j' : 'y'] = point.y;
return this._url + L.Util.getParamString(params, this._url, true);
},
showGetFeatureInfo: function (err, latlng, content) {
if (err) { console.log(err); return; } // do nothing if there's an error
// Otherwise show the content in a popup, or something.
L.popup({ maxWidth: 800})
.setLatLng(latlng)
.setContent(content)
.openOn(this._map);
}
});
L.tileLayer.betterWms = function (url, options) {
return new L.TileLayer.BetterWMS(url, options);
};
<!-- *****************map******************************** -->
var map = L.map('mapid', {
<!-- center: [51.1961, 13.3105], -->
<!-- zoom: 15 -->
center: [50.8, 14],
zoom: 11
});
var basemaps = {
OSM: L.tileLayer('https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}{r}.png', {
minZoom: 4,
maxZoom: 19,
attribution: 'Map tiles by <a target="_blank" href="http://cartodb.com/attributions">CartoDB</a> | Map data by <a target="_blank" href="http://www.openstreetmap.org/copyright" >OpenStreetMap</a>',
subdomains: 'abcd'
}),
Boundaries: L.tileLayer.betterWms('https://demo.boundlessgeo.com/geoserver/ows?', {
layers: 'ne:ne_10m_admin_0_boundary_lines_land',
transparent: true,
format: 'image/png'
}),
FNP: L.tileLayer.betterWms("https://rz.ipm-gis.de/ags01/services/RAPIS/RAPIS_FNP/MapServer/WmsServer?", {
layers: '1',
format: 'image/png',
transparent: true,
attribution: 'WMS: <a target="_blank" href="https://www.geomis.sachsen.de/terraCatalog/Query/ShowCSWInfo.do?fileIdentifier=a57376e1-1de2-48f7-b125-0b43c5089d91">Flächennutzungsplan RAPIS</a>'
})
};
L.control.layers(basemaps, {}, {collapsed: false}).addTo(map);
basemaps.FNP.addTo(map);
</script>
</body>
</html>
It does not work for the WMS 'FNP'. When I change the 'propertyName' to 'name' to test the request of the wms 'Boundaries' it works. So maybe some expressions are missing in the WMS 'FNP'? Both WMS are external and not published by myself.
Here are the sources/getCapabilities of both WMS:
WMS 'FNP': https://rz.ipm-gis.de/ags01/services/RAPIS/RAPIS_FNP/MapServer/WmsServer?REQUEST=GetCapabilities&SERVICE=WMS
WMS 'Boundaries': https://demo.boundlessgeo.com/geoserver/ows?service=wfs&version=1.0.0&request=GetCapabilities
Do you have any idea why the instruction with the one wms works but not with the other one?
I would be very grateful for any help! Thank you!
Do you have any idea why the instruction with the one wms works but not with the other one?
The service you refer to under the heading WMS 'Boundaries is not a WMS it's a WFS. WFS do no support an operation called GetFeatureInfo only WMS support that operation.
Related
I have changed my mind about how I want to use the Checkbox filter in Mapbox. I am using CSV2Geojson to map Google Sheets data in my map and then filter by values in one column. I know there is this https://labs.mapbox.com/education/impact-tools/finder-with-filters/ but I want to stick to my layout instead of using a config file to define options. My original spreadsheet had a function that looked at column headers and only returned cells with "Y" in them when checked. Now, I have 1 column where I need to look for 4 specific values. Below is the code and codepen for debugging. The code in question is at the bottom of the code block.
https://codepen.io/bearcats6001/project/editor/AjrPPP
<!DOCTYPE html>
<html lang="en" >
<head>
<meta charset="UTF-8">
<title>CodePen - project map 2</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<!-- partial:index.partial.html -->
<meta name='viewport' content='width=device-width, initial-scale=1'>
<link href="https://api.mapbox.com/mapbox-gl-js/v2.7.0/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.7.0/mapbox-gl.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.0/jquery.min.js"></script>
<script src='https://npmcdn.com/csv2geojson#latest/csv2geojson.js'></script>
<script src='https://npmcdn.com/#turf/turf/turf.min.js'></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans&display=swap" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.7.2/mapbox-gl-geocoder.min.js"></script>
<link rel="stylesheet" href="https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-geocoder/v4.7.2/mapbox-gl-geocoder.css" type="text/css">-->
<div id="map">
<div id="filterMenu">
<h3>Programs</h3>
<input type="checkbox" id="Nathan" name="srBox" value="SustainableRecreation">
<label for="Nathan"> Nathan Shock Centers of Excellence in the Basic Biology of Aging</label><br>
<input type="checkbox" id="Pepper" name="hlBox" value="HealthyLandscapes">
<label for="Pepper"> Claude D. Pepper Older Americans Independence Centers</label><br>
<input type="checkbox" id="Aging" name="ovBox" value="Overnight">
<label for="Aging"> Centers on the Demography and Economics of Aging</label><br>
<input type="checkbox" id="Minority" name="ovBox" value="Overnight">
<label for="Minority"> Resource Centers for Minority Aging Research</label><br>
</div>
</div>
<!-- partial -->
<script src="./script.js"></script>
</body>
</html>
var transformRequest = (url, resourceType) => {
var isMapboxRequest =
url.slice(8, 22) === "api.mapbox.com" ||
url.slice(10, 26) === "tiles.mapbox.com";
return {
url: isMapboxRequest
? url.replace("?", "?pluginName=sheetMapper&")
: url,
};
};
mapboxgl.accessToken = 'pk.eyJ1IjoiYWZhcm9yZyIsImEiOiJjbDIwajV3YTUwMGc3M2xwNDdiYWJiMjUzIn0.Pjt9AndPk1Axv99wez-5TA';
const map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/light-v10',
center: [-102.182698, 37.131563],
zoom: 3,
//left, bottom, right, top
});
var nav = new mapboxgl.NavigationControl({
showCompass: true,
showZoom: true,
visualizePitch: true
});
map.addControl(nav, "bottom-right");
$(document).ready(function () {
$.ajax({
type: "GET",
url: 'https://docs.google.com/spreadsheets/d/1umfhXq5WEPLEABV81-tZUayAw7WZrmqe/gviz/tq?tqx=out:csv&sheet=Sheet1',
dataType: "text",
success: function (csvData) { makeGeoJSON(csvData); }
});
function makeGeoJSON(csvData) {
csv2geojson.csv2geojson(csvData, {
latfield: 'Latitude',
lonfield: 'Longitude',
delimiter: ','
}, function (err, data) {
map.on('load', function () {
map.addLayer({
'id': 'rfovProjects',
'type': 'circle',
'source': {
'type': 'geojson',
'data': data
},
'paint': {
'circle-radius': 7,
'circle-color': "blue",
'circle-opacity': 0.5,
}
});
});
});
};
});
map.on('mouseenter', 'rfovProjects', function () {
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'rfovProjects', function () {
map.getCanvas().style.cursor = '';
});
map.on('click', function(e) {
if(!e.originalEvent.defaultPrevented) {
e.originalEvent.preventDefault();
}
var features = map.queryRenderedFeatures(e.point, {
layers: ['rfovProjects']
});
if (!features.length) {
return;
}
var feature = features[0];
var popupContent = '<h3 style="display:inline">' + feature.properties.Name + '</h3><br><p style="display:inline"><b>'
/*popupContent += feature.properties.time ? '</b>, ' + feature.properties.time : '</b>'*/
popupContent += '<br> Website Link </p>'
var popup = new mapboxgl.Popup({ offset: [0, 0] })
.setLngLat(e.lngLat)
.setHTML(popupContent)
.addTo(map);
});
//basemap toggles
/*
var tRadio = document.getElementById('topoRadio');
tRadio.addEventListener('change', function() {
if (this.checked) {
map.setLayoutProperty('mapbox-satellite', 'visibility', 'none');
}
});
var iRadio = document.getElementById('imageryRadio');
iRadio.addEventListener('change', function() {
if (this.checked) {
map.setLayoutProperty('mapbox-satellite', 'visibility', 'visible');
}
});*/
//
map.doubleClickZoom.enable();
map.on('dblclick', function(e) {
map.getZoom() +10;
});
map.addControl(
new MapboxGeocoder({
accessToken: mapboxgl.accessToken,
mapboxgl: mapboxgl,
flyTo: {
zoom: 12,
easing: function(t) {
return t;
}
},
marker: false
})
);
map.addControl(new mapboxgl.NavigationControl());
var filters = {};
function updateFilters() {
var compositeFilter = ['all'];
for (let filterValue in filters) {
if (filters[filterValue]) {
compositeFilter.push(['==', ['get', filterValue], 'Y']);
}
}
if (compositeFilter.length > 1)
map.setFilter('rfovProjects', compositeFilter);
else {
map.setFilter('rfovProjects', null);
}
}
var checkbox = document.getElementById('Nathan');
checkbox.addEventListener('change', function() {
filters['Program'] = this.checked;
updateFilters();
});
var checkbox = document.getElementById('Pepper');
checkbox.addEventListener('change', function() {
filters['Program'] = this.checked;
updateFilters();
});
var checkbox = document.getElementById('Aging');
checkbox.addEventListener('change', function() {
filters['Program'] = this.checked;
updateFilters();
});
var checkbox = document.getElementById('Minority');
checkbox.addEventListener('change', function() {
filters['Program'] = this.checked;
updateFilters();
});
I am using google map api, geolocation.
I want to update user's location, when user logs in or signs up.
But in my firebase database, user's location is null and I can not see a marker at all.
Here is my code. I changed getCurrentPosition to watchPosition once, but it did not work. And in my console, nothing shows up.
My firebase is the latest version. If you need some more codes to answer, please ask me.
When I log in, my place is correctly set, and my location is put center.
<template>
<div class="map">
<div class="google-map" id="map"></div>
</div>
</template>
<script>
import firebase from 'firebase'
import db from '#/firebase/init'
export default {
name: 'GMap',
data() {
return {
lat: 53,
lng: -2
}
},
methods: {
renderMap() {
const map = new google.maps.Map(document.getElementById('map'), {
center: {lat: this.lat , lng: this.lng },
zoom: 6,
maxZoom: 15,
minZoom: 3,
streetViewControl: false
})
db.collection('users').get().then(users => {
users.docs.forEach(doc => {
let data = doc.data()
if(data.geolocation) {
let marker = new google.maps.Marker({
position: {
lat: data.geolocation.lat,
lng: data.geolocation.lng
},
map
})
//add click event to marker
marker.addListener('click', () => {
//console.log(doc.id)
this.$router.push({ name: 'ViewProfile', params: { id: doc.id }} )
})
}
})
})
}
},
mounted() {
//get current user
let user = firebase.auth().currentUser
//get user geolocation
if(navigator.geolocation) {
navigator.geolocation.getCurrentPosition(pos => {//pos=position
this.lat = pos.coords.latitude
this.lng = pos.coords.longitude
//find user record and then update geocoords
db.collection('users').where('user_id', '==', 'cred.user.uid').get()
.then(snapshot => {
snapshot.forEach((doc) => {
db.collection('users').doc(doc.id).update({
geolocation: {
lat: pos.coords.latitude,
lng: pos.coords.longitude,
}
})
})
}).then(() => {
this.renderMap()
})
}, (err) => {
console.log(err)
this.renderMap()
}, { enableHighAccuracy: true, maximumAge: 60000, timerout: 3000 })
} else {
//position centre by default values
this.renderMap()
}
}
}
</script>
<style lang="">
.google-map {
width: 100%;
height: 100%;
margin: 0 auto;
background: #fff;
position: absolute;
top: 0;
left: 0;
z-index: -1;
}
</style>
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<!--Import Google Icon Font-->
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<!-- Compiled and minified CSS -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB9n8HHOm0YVLwSf6tbuU3p69t8bqlUVSE"
type="text/javascript"></script>
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>geo-ninjas</title>
</head>
<body>
<noscript>
<strong>We're sorry but geo-ninjas doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
I'm trying to locate the user when the website is fully loaded.
I'm using the newest MapBox API (JavaScript)
Is it possible to do that without requiring the user to click on the top right button on the map?
var map = new mapboxgl.Map({
container: 'map',
style: 'mapbox://styles/mapbox/streets-v11',
center: [0,0],
zoom: 15 // starting zoom
});
// Add geolocate control to the map.
map.addControl(new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true
}));
try with this example, it's work for me
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8' />
<title></title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://cdnjs.cloudflare.com/ajax/libs/mapbox-gl/0.53.1/mapbox-gl.js'></script>
<link href='https://cdnjs.cloudflare.com/ajax/libs/mapbox-gl/0.53.1/mapbox-gl.css' rel='stylesheet' />
<style>
body { margin:0; padding:0; }
#map { position:absolute; top:0; bottom:0; width:100%; }
</style>
<script >
var get_location = function() {
var geolocation = null;
var c_pos = null;
if (window.navigator && window.navigator.geolocation) {
geolocation = window.navigator.geolocation;
var positionOptions = {
enableHighAccuracy: true,
timeout: 10 * 1000, // 10 seconds
maximumAge: 30 * 1000 // 30 seconds
};
function success(position) {
console.log(position);
c_pos = position.coords;
mapboxgl.accessToken = 'token'; ///////////////// put your token here
if (!mapboxgl.supported()) {
alert('Your browser does not support Mapbox GL');
} else {
var map = new mapboxgl.Map({
container: 'map', // container id
style: 'mapbox://styles/mapbox/streets-v11',
center: [c_pos.longitude, c_pos.latitude],
zoom: 12 // starting zoom
});
}
}
function error(positionError) {
console.log(positionError.message);
}
if (geolocation) {
geolocation.getCurrentPosition(success, error, positionOptions);
}
} else {
alert("Getting Geolocation is prevented on your browser");
}
return c_pos;
}
</script>
</head>
<body>
<div id='map'></div>
<script>
var current_pos = get_location();
</script>
</body>
</html>
try with this
navigator.geolocation.getCurrentPosition(position => {
const userCoordinates = [position.coords.longitude, position.coords.latitude];
map.addSource("user-coordinates", {
type: "geojson",
data: {
type: "Feature",
geometry: {
type: "Point",
coordinates: userCoordinates
}
}
});
map.addLayer({
id: "user-coordinates",
source: "user-coordinates",
type: "circle"
});
map.flyTo({
center: userCoordinates,
zoom: 14
});
});
Yes, you have to use trigger() to activate the tracking in a programmed way.
// Initialize the geolocate control.
var geolocate = new mapboxgl.GeolocateControl({
positionOptions: {
enableHighAccuracy: true
},
trackUserLocation: true
});
// Add the control to the map.
map.addControl(geolocate);
map.on('load', function() {
geolocate.trigger(); //<- Automatically activates geolocation
});
see https://docs.mapbox.com/mapbox-gl-js/api/markers/#geolocatecontrol-instance-members
I am trying to recreate the following map, made using Google MyMaps, by using Google Maps API
Anyone know the best way to do this? Have the following code at the moment but doesn't look great
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Search for up to 200 places with Radar Search</title>
<style>
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
</style>
<script>
var map;
var infoWindow;
var service;
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: {
lat: 54.607868,
lng: -5.926437
},
zoom: 10,
styles: [{
stylers: [{
visibility: 'simplified'
}]
}, {
elementType: 'labels',
stylers: [{
visibility: 'off'
}]
}]
});
infoWindow = new google.maps.InfoWindow();
service = new google.maps.places.PlacesService(map);
// The idle event is a debounced event, so we can query & listen without
// throwing too many requests at the server.
map.addListener('idle', performSearch);
}
function performSearch() {
var request = {
bounds: map.getBounds(),
keyword: 'best view'
};
service.radarSearch(request, callback);
}
function callback(results, status) {
if (status !== google.maps.places.PlacesServiceStatus.OK) {
console.error(status);
return;
}
for (var i = 0, result; result = results[i]; i++) {
addMarker(result);
}
}
function addMarker(place) {
var marker = new google.maps.Marker({
map: map,
position: place.geometry.location,
icon: {
url: 'https://developers.google.com/maps/documentation/javascript/images/circle.png',
anchor: new google.maps.Point(10, 10),
scaledSize: new google.maps.Size(10, 17)
}
});
google.maps.event.addListener(marker, 'click', function() {
service.getDetails(place, function(result, status) {
if (status !== google.maps.places.PlacesServiceStatus.OK) {
console.error(status);
return;
}
infoWindow.setContent(result.name);
infoWindow.open(map, marker);
});
});
}
</script>
</head>
<body>
<div id="map"></div>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCIcOfMnc85XkuJmotWkLL4mthAHqlUuWA&callback=initMap&libraries=places,visualization" async defer></script>
</body>
</html>
use a saved google my maps and share it from your google drive
I'm trying to create a Google Fusion table heatmap. I've copy and pasted the code from here and it works fine on localhost. I then change my API key and column name to loc and use my fusion table id AIzaSyAoiA3rVUoIRrsxje8JTWr599YqnHpHvCA but it just isn't rendering.
What am I missing? I'm guessing it's a setting my fusion table itself but I can't see any other configurable options. I've shared the table publically.
var layer = new google.maps.FusionTablesLayer({
query: {
select: 'loc',
from: '1vtep10enWGFG1NBblFkycgjOiD6O-QKif3N3Ku9G'
},
heatmap: {
enabled: true
}
});
layer.setMap(map);
}
</script>
<script async defer
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAoiA3rVUoIRrsxje8JTWr599YqnHpHvCA&callback=initMap">
My complete HTML code is
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<title>Fusion Tables heatmaps</title>
<style>
html, body {
height: 100%;
margin: 0;
padding: 0;
}
#map {
height: 100%;
}
</style>
</head>
<body>
<div id="map"></div>
<script>
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 50.813215, lng: -0.407896},
zoom: 8
});
var layer = new google.maps.FusionTablesLayer({
query: {
select: 'loc',
from: '1vtep10enWGFG1NBblFkycgjOiD6O-QKif3N3Ku9G'
},
heatmap: {
enabled: true
}
});
layer.setMap(map);
}
</script>
<script async defer
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAoiA3rVUoIRrsxje8JTWr599YqnHpHvCA&callback=initMap">
</script>
</body>
</html>