How to fix searchbox error ini google maps - javascript

I'm having problems with Google Maps. if what I did the first time was to choose a location on google maps, after that I typed the other location in the searchbox there was no problem. but when I type the location first in the searchbox without selecting the location at the beginning there is an error.
TypeError: marker.setPosition is not a function
<script type="text/javascript">
var map;
var marker = false;
function initMap() {
<?php if ($customer['lat'] != '0.00000000' && $customer['lng'] != '0.00000000'): ?>
var myLatLng = {lat: <?php echo $customer['lat']; ?>, lng: <?php echo $customer['lng']; ?>};
var centerOfMap = new google.maps.LatLng(<?php echo $customer['lat']; ?>, <?php echo $customer['lng']; ?>);
<?php else: ?>
var centerOfMap = new google.maps.LatLng(-6.2115, 106.8452);
<?php endif; ?>
var options = {
center: centerOfMap,
zoom: 15
};
map = new google.maps.Map(document.getElementById('map'), options);
<?php if ($customer['lat'] != '0.00000000' && $customer['lng'] != '0.00000000'): ?>
marker = new google.maps.Marker({
position: myLatLng,
map: map,
draggable: true
});
<?php endif; ?>
// Create the search box and link it to the UI element.
var input = document.getElementById('pac-input');
var searchBox = new google.maps.places.SearchBox(input);
map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
// Bias the SearchBox results towards current map's viewport.
map.addListener('bounds_changed', function() {
searchBox.setBounds(map.getBounds());
});
var markers = [];
searchBox.addListener('places_changed', function() {
var places = searchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
var bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
if (!place.geometry) {
console.log("Returned place contains no geometry");
return;
}
marker.setPosition(place.geometry.location);
map.setCenter(place.geometry.location);
});
});
google.maps.event.addListener(map, 'click', function(event) {
var clickedLocation = event.latLng;
if (marker === false){
marker = new google.maps.Marker({
position: clickedLocation,
map: map,
draggable: true
});
google.maps.event.addListener(marker, 'dragend', function(event){
markerLocation();
});
} else{
marker.setPosition(clickedLocation);
}
});
}
$(function(){
$('#save-map').on('click touchstart', function(){
var currentLocation = marker.getPosition();
$('input[name="lat"]').val(currentLocation.lat());
$('input[name="lng"]').val(currentLocation.lng());
$('#mapModal').modal('hide');
$('#img-map').html('<img src="https://maps.googleapis.com/maps/api/staticmap?center='+currentLocation.lat()+','+currentLocation.lng()+'&markers='+currentLocation.lat()+','+currentLocation.lng()+'&scale=2&size=640x100&zoom=15&key=<?php echo getenv('GOOGLE_MAP_API_KEY'); ?>" class="img-fluid" style="width: 100%;">');
});
});
</script>
how do i modify this javascript script so there are no problems. if the user types the address directly in the search box the first time the marker will run as desired

I replaced the var marker & defined the position map when the user directly typed in the searchbox column.
var map;
var marker = [];
function initMap() {
<?php if ($customer['lat'] != '0.00000000' && $customer['lng'] != '0.00000000'): ?>
var myLatLng = {lat: <?php echo $customer['lat']; ?>, lng: <?php echo $customer['lng']; ?>};
var centerOfMap = new google.maps.LatLng(<?php echo $customer['lat']; ?>, <?php echo $customer['lng']; ?>);
<?php else: ?>
var centerOfMap = new google.maps.LatLng(-6.2115, 106.8452);
<?php endif; ?>
var options = {
center: centerOfMap,
zoom: 15
};
map = new google.maps.Map(document.getElementById('map'), options);
<?php if ($customer['lat'] != '0.00000000' && $customer['lng'] != '0.00000000'): ?>
marker = new google.maps.Marker({
position: myLatLng,
map: map,
draggable: true
});
<?php else: ?>
marker = new google.maps.Marker({
position: centerOfMap,
map: map,
draggable: true
});
<?php endif; ?>
// Create the search box and link it to the UI element.
var input = document.getElementById('pac-input');
var searchBox = new google.maps.places.SearchBox(input);
map.controls[google.maps.ControlPosition.TOP_LEFT].push(input);
// Bias the SearchBox results towards current map's viewport.
map.addListener('bounds_changed', function() {
searchBox.setBounds(map.getBounds());
});
var markers = [];
searchBox.addListener('places_changed', function() {
var places = searchBox.getPlaces();
if (places.length == 0) {
return;
}
// For each place, get the icon, name and location.
var bounds = new google.maps.LatLngBounds();
places.forEach(function(place) {
if (!place.geometry) {
console.log("Returned place contains no geometry");
return;
}
marker.setPosition(place.geometry.location);
map.setCenter(place.geometry.location);
});
});
google.maps.event.addListener(map, 'click', function(event) {
var clickedLocation = event.latLng;
if (marker === false){
marker = new google.maps.Marker({
position: clickedLocation,
map: map,
draggable: true
});
google.maps.event.addListener(marker, 'dragend', function(event){
markerLocation();
});
} else{
marker.setPosition(clickedLocation);
}
});
}
$(function(){
$('#save-map').on('click touchstart', function(){
var currentLocation = marker.getPosition();
$('input[name="lat"]').val(currentLocation.lat());
$('input[name="lng"]').val(currentLocation.lng());
$('#mapModal').modal('hide');
$('#img-map').html('<img src="https://maps.googleapis.com/maps/api/staticmap?center='+currentLocation.lat()+','+currentLocation.lng()+'&markers='+currentLocation.lat()+','+currentLocation.lng()+'&scale=2&size=640x100&zoom=15&key=<?php echo getenv('GOOGLE_MAP_API_KEY'); ?>" class="img-fluid" style="width: 100%;">');
});
});

Related

How to manage two search bar and dropdown box in Googlemap Javascript API

<div class="row">
<table>
<td>
<div class="col-md-5">
<select class="form-control" name="companyID">
<option value="0">All companies</option>
</select>
</div>
<button class="btn btn-primary">Search</button>
</td>
<td>
<input id="mapsearch" type="text" placeholder="Search Box" onclick="initAutocomplete()">
</td>
</table>
</div>
<div id="map"></div>
<script>
function initialize(){
initMap();
initAutocomplete();
}
function initMap(){
<?php
if($CompanyUserLocation) {
for($i=0; $i< count($CompanyUserLocation); $i++){
if(empty($fromLat[$i]) || empty($fromLon[$i])) {
continue;
}
?>
var options = {
zoom:10,
center:{
lat: <?php echo $fromLat[$i]; ?>,
lng: <?php echo $fromLon[$i]; ?>
}
}
<?php } } ?>
var map = new google.maps.Map(document.getElementById('map'), options);
<?php
for($i=0; $i< count($CompanyUserLocation); $i++){
if(empty($fromLat[$i]) || empty($fromLon{$i})) {
continue;
}
?>
var markers = [
{
coords:{
lat: <?php echo $fromLat[$i];?>,
lng: <?php echo $fromLon[$i];?>
},
content: '<h1><?php echo $CompanyUserName[$i]; ?></h1>'
}
];
for(var i = 0;i < markers.length;i++){
// Add marker
addMarker(markers[i]);
}
function addMarker(props){
var marker = new google.maps.Marker({
position:props.coords,
map:map,
//icon:props.iconImage
});
// Check for customicon
if(props.iconImage){
// Set icon image
marker.setIcon(props.iconImage);
}
// Check content
if(props.content){
var infoWindow = new google.maps.InfoWindow({
content:props.content
});
marker.addListener('click', function(){
infoWindow.open(map, marker);
});
}
}
<?php
} ?>
}
function initAutocomplete() {
var map = new google.maps.Map(document.getElementById('map'), {
center: {lat: -33.8688, lng: 151.2195},
zoom: 13,
});
var marker = new google.maps.Marker({
position:{
lat: 27.72,
lng: 85.36
},
map:map
});
var searchBox = new google.maps.places.SearchBox(document.getElementById('mapsearch'));
google.maps.event.addListener(searchBox, 'places_changed', function(){
var places = searchBox.getPlaces();
var bounds = new google.maps.LatLngBounds();
var i, place;
for(i=0; place=places[i];i++) {
bounds.extend(place.geometry.location);
marker.setPosition(place.geometry.location);
}
map.fitBounds(bounds);
map.setZoom(11);
});
}
</script>
<script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?key=AIzaSyDK-vhUAcKLUzMWKbSzIxP2Wv3mZrsgNzE&language=de&libraries=places&callback=initialize"
async defer></script>
I created search bar(for search google place address) and dropdown(for find the riders location) box dynamically. Now its worked. But its have some issue.
That is, If I search address in textbox, it showing the searched place. Its right. But after that, If i try to search in dropdown box for find the riders location, its displaying that place after double click an dropdown enter button.
As same as, if i first searched the riders location, its shown correct. But after that if i try to find google address in text box, autocomplete function is not working. what can I do? please help me. I am new for PHP and javascript.

how to use marker cluster with php mysql database

i have PHP code with MYSQL database and javascript where i ma using the java script in order to display the Google Map with markers where the code display markers based on their coordination.
i have some markers that have the same coordination but different ID so the map show just the last marker.
what i need is:
either to display all the markers that have the same coordination
or
display one marker with number that indicate the the total number of
the markers that have the same coordination.
code:
<script type="text/javascript">
var map,currentPopup;
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
zoom: 8,
center: new google.maps.LatLng(33.888630, 35.495480),
mapTypeId: 'roadmap'
});
var iconBase = 'https://maps.google.com/mapfiles/kml/shapes/';
var icons = {
parking: {
icon: iconBase + 'parking_lot_maps.png'
},
library: {
icon: iconBase + 'library_maps.png'
},
info: {
icon: iconBase + 'info-i_maps.png'
}
};
function addMarker(feature) {
var marker = new google.maps.Marker({
position: feature.position,
//icon: icons[feature.type].icon,
map: map
});
var markerCluster = new MarkerClusterer(map, marker,
{imagePath:
'https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/m'});
var popup = new google.maps.InfoWindow({
content: '<b>Site ID :</b> ' + feature.info +'<br></br>'
+ '<b> Site Name :</b>' + feature.site_name +'<br></br>'
+ '<b>Coordinates :</b> '+ feature.position +'<br></br>'
+ '<b>height :</b> ' + feature.height,
maxWidth: 300
});
google.maps.event.addListener(marker, "click", function() {
if (currentPopup != null) {
currentPopup.close();
currentPopup = null;
}
popup.open(map, marker);
currentPopup = popup;
});
google.maps.event.addListener(popup, "closeclick", function() {
map.panTo(center);
currentPopup = null;
});
}
var features = [
<?php
$prependStr ="";
foreach( $wpdb->get_results("select c.siteID, c.latitude, c.longitude, c.height
, i.siteNAME
FROM site_coordinates2 c LEFT
JOIN site_info i
on c.siteID = i.siteID
where i.siteNAME = '".$site_name."'", OBJECT) as $key => $row) {
$latitude = $row->latitude;
$longitude = $row->longitude;
$siteid = $row->siteID;
$height = $row->height;
$siteName = $row->siteNAME;
echo $prependStr;
?>
{
position: new google.maps.LatLng(<?php echo $latitude; ?>, <?php echo $longitude; ?>),
info:'<?php echo $siteid;?>',
height: '<?php echo $height;?>',
site_name: '<?php echo $siteName;?>',
}
<?php
$prependStr =",";
}
?>
];
for (var i = 0, feature; feature = features[i]; i++) {
addMarker(feature);
}
}
</script>
<?php
//echo "</table>";
}
?>
and i add this library to the code
<script src="https://developers.google.com/maps/documentation/javascript/examples/markerclusterer/markerclusterer.js">
</script>

Getting gps Lat, Long values from DataBase and Draw marker on map?

I am working on a tracking system.
I get gps lat long values from database and then show marker on map,
and refresh a page after 30 seconds to check may b values updated on server.
my code is given below.
may map show page.
<!DOCTYPE html />
<html>
<head>
<script src="http://code.jquery.com/jquery-1.11.0.min.js" type="text/javascript"> </script>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"> </script>
<title>GoogleMaps</title>
<script>
function initialize(lang,lat) {
var myLatlng = new google.maps.LatLng(lang,lat);
var myOptions = {
zoom: 8,
center: myLatlng,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
var contentString = 'Location NAme';
var infowindow = new google.maps.InfoWindow({
content: contentString
});
var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
var marker = new google.maps.Marker({
position: myLatlng,
map: map,
title: 'Location NAme'
});
}
function show_map(){
$.ajax({
type: "POST",
url: "some.php"
})
.done(function( msg ) {
alert(msg);
var obj = jQuery.parseJSON(msg);
initialize(obj.lang,obj.lat);
});
}
setInterval(show_map,30000);
</script>
</head>
<body onLoad="show_map()">
<div id="map_canvas" style="width: 100%; height: 100%;">
</body>
</html>
my PHP page is:
<?php
mysql_connect("localhost","root","")or die("error in connection");
mysql_select_db("gpsvalues");
$result = mysql_query("select * from gps");
$row = mysql_fetch_assoc($result);
$arr=array();
$arr = $row;
echo json_encode($arr);
?>
But it is not works fine.
For show multiple in a map you have to create array for lat long.
var locations;
locations=[
[lat1,long1],
[lat2,long2],
[lat3,long3],
[lat4,long4],
];
So your code should be change like
<script>
var locations;
locations=[
[lat1,long1],
[lat2,long2],
[lat3,long3],
[lat4,long4],
];
var map = new google.maps.Map(document.getElementById('maparea'), {
zoom: 14,
center: new google.maps.LatLng(6.778659644259302, 79.99540328979492),
mapTypeId: google.maps.MapTypeId.ROADMAP
});
var infowindow = new google.maps.InfoWindow();
var marker, i;
for (i = 0; i < locations.length; i++) {
marker = new google.maps.Marker({
position: new google.maps.LatLng(locations[i][0], locations[i][1]),
map: map
});
google.maps.event.addListener(marker, 'click', (function(marker, i) {
return function() {
infowindow.setContent(locations[i][0]);
infowindow.open(map, marker);
}
})(marker, i));
}
</script>
You can call the add marker function through your php for every row in the gps..
<script>
<?php
mysql_connect("localhost","root","")or die("error in connection");
mysql_select_db("gpsvalues");
$result = mysql_query("select * from gps");
while($row = mysql_fetch_assoc($result))
{
echo "addmarker($row[lat], $row[lng]);"; //whatever way you have stored in your database
}
?>
function addmarker(lat, lng) {
var myLatlng = new google.maps.LatLng(lat,lng);
var marker = new google.maps.Marker({
position: myLatlng,
map: map,
title: 'Location NAme'
});
marker.setMap(map);
}
</script>

How to display location on markers of google maps when clicked?

I have extracted the latitude and logitude from my database tables and have shown it on the google maps with markers.Now I want to get location to be displayed on the markers when they are clicked.I tried a lot but couldn't find the way.Can you please suggest how shall I proceeed?I am also giving the sample code which may help you.Kindly help me out.
<?php
$dbname='140dev';
$dbuser='root';
$dbpass='root';
$dbserver='localhost';
$dbcnx = mysql_connect ("$dbserver", "$dbuser", "$dbpass");
mysql_select_db("$dbname") or die(mysql_error());
$sql = mysql_query("SELECT * FROM tweets1");
$res = mysql_fetch_array($sql);
$lat_d = $res['geo_lat'];
$long_d = $res['geo_long'];
// mimic a result array from MySQL
$result = array(array('geo_lat'=>$lat_d,'geo_long'=>$long_d));
?>
<!doctype html>
<html>
<head>
<script type="text/javascript"
src="http://maps.googleapis.com/maps/api/js?key="API_key"&sensor=false"></script>
<script type="text/javascript">
var map;
function initialize() {
// Set static latitude, longitude value
var latlng = new google.maps.LatLng(<?php echo $lat_d; ?>, <?php echo $long_d; ?>);
// Set map options
var myOptions = {
zoom: 16,
center: latlng,
panControl: true,
zoomControl: true,
scaleControl: true,
mapTypeId: google.maps.MapTypeId.ROADMAP
}
// Create map object with options
map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
<?php
// uncomment the 2 lines below to get real data from the db
$result = mysql_query("SELECT * FROM tweets1");
while ($row = mysql_fetch_array($result))
echo "addMarker(new google.maps.LatLng(".$row['geo_lat'].", ".$row['geo_long']."),
map);";
?>
}
function addMarker(latLng, map) {
var marker = new google.maps.Marker({
position: latLng,
map: map,
draggable: true, // enables drag & drop
animation: google.maps.Animation.DROP
});
var contentString = latLng.lat() + " , " + latLng.lng();
geocoder.geocode({'latLng': latlng}, function(results, status) {
if (status == google.maps.GeocoderStatus.OK) {
if (results[1]) {
map.setZoom(11);
marker = new google.maps.Marker({
position: latlng,
map: map
});
infowindow.setContent(results[1].formatted_address);
infowindow.open(map, marker);
} else {
alert('No results found');
}
} else {
alert('Geocoder failed due to: ' + status);
}
});
google.maps.event.addListener(marker, 'click', function() {
infowindow.open(map,marker);
});
}
</script>
</head>
<body onload="initialize()">
<div style="float:left; position:relative; width:550px; border:0px #000 solid;">
<div id="map_canvas" style="width:950px;height:700px;border:solid black 1px;">
</div>
</div>
</body>
</html>
Improve your addMarker() function as it shows lat-lng values of clicked marker on infowindow:
function addMarker(latLng, map) {
var marker = new google.maps.Marker({
position: latLng,
map: map,
draggable: true, // enables drag & drop
animation: google.maps.Animation.DROP
});
var contentString = latLng.lat() + " - " + latLng.lng();
var infowindow = new google.maps.InfoWindow({
content: contentString
});
google.maps.event.addListener(marker, 'click', function() {
infowindow.open(map,marker);
});
}
You can add location info as marker label. Just follow the example
http://google-maps-utility-library-v3.googlecode.com/svn/tags/markerwithlabel/1.1.8/examples/basic.html

Every infowindow is displaying the same data maps api v3

I am really stuck on something. Every map marker's infowindow is displaying the same info. It seems to be the content at the end of an array that i use to store content nodes every time. I am pretty sure it is because the infowindow is not being attached to the proper marker
var markers = [];
var contentArray = [];
var titleArray = [];
var latlngArray = [];
var map;
//var infowindow;
var concert;
function defaultMap()
{
//Latitude: 38
//Longitude: -97
//window.alert("inside function");
var mapOptions = {
center:new google.maps.LatLng(38,-97),
zoom:4,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
map = new google.maps.Map(document.getElementById("map"),
mapOptions);
// window.alert("addMarkers the size of contentArray is: "+contentArray.length);
//window.alert("addMarkers the size of the titleArray is: "+titleArray.length);
// window.alert("addMarkers the size of the latLongArray is: "+latlngArray.length);
//for(var i =0;i<2;i++)
//{
// if(i == 0)
// {
// marker = new google.maps.Marker({
// position: new google.maps.LatLng(37.8172784,-96.8909115),
// map:map
// });
// markers.push(marker);
// }
// else
// {
// marker = new google.maps.Marker({
// position: new google.maps.LatLng(37.8172973,-96.8766355),
// map:map
// });
// markers.push(marker);
// }
// //markers[0] = new google.maps.LatLng(37.8172784,-96.8909115);
// //markers[1] = new google.maps.LatLng(37.8172973,-96.8766355);
//
//}
//addMarkers();
}
//function
//
//{
//infowindow = new google.maps.InfoWindow({
//content:list
//});
//google.maps.event.addListener(marker,'click',function(){
// infowindow.open(map,marker);
//});
function addMarkers()
{
//console.dir(contentArray[contentArray.length-1]);
for(var i = 0;i <10;i++)
{
if(i == 0)
{
//window.alert("i = "+i);
console.log(latlngArray[i]);
var marker = new google.maps.Marker({
position:latlngArray[i],
animation:google.maps.Animation.DROP,
icon:'./images/club.png',
title:titleArray[i],
map:map
});
//marker.setMap(map);
var infowindow = new google.maps.InfoWindow({
});
google.maps.event.addListener(marker,'click',function()
{
//console.log(infowindow.getContent());
infowindow.setContent(contentArray[i]);
infowindow.open(map,this);
});
markers.push(marker);
}
else
{
console.log(latlngArray[i]);
var marker = new google.maps.Marker({
position:latlngArray[i],
animation:google.maps.Animation.DROP,
icon:'./images/restaurant.png',
title:titleArray[i],
map:map
});
var infowindow = new google.maps.InfoWindow({});
//console.log(infowindow.getContent());
google.maps.event.addListener(marker,'click',function()
{
infowindow.setContent(contentArray[i]);
console.log(infowindow.getContent());
infowindow.open(map,this);
});
markers.push(marker);
}
//console.log(i);
//console.log(contentArray[i]);
}
}
The problem is that when the loop ends, i is 10.
Every infowindow displays:
infowindow.setContent(contentArray[i]);
There are two ways to solve the problem:
function closure. Use a createMarker function to associate the infowindow content with the marker. Explained in Mike Williams' v2 tutorial, one of his examples using function closure, translated to v3.
marker member variable containing the content, access it in the click listener by referencing "this". The answer to this similar question may help with this. Here is an example of using a member variable of the marker
This Code is also for all those who want to put multiple Markers on Map retrieved from DB
i am going to paste a Code of live project means working. you can get some help from this.
function latLongCallback(latitutde,longitutde){
var latlng = new google.maps.LatLng(latitutde, longitutde);
var options = {zoom: 4, center: latlng, mapTypeId: google.maps.MapTypeId.ROADMAP};
var map = new google.maps.Map(document.getElementById('map'), options);
$.ajax({type: "GET",
dataType: 'json',
url: 'https://www.xyz.com/yourrfolder/markers.php',
success: function(response){
var total=response.length;
var data_array,name,type,address,lat,lon,arrival,departure,notes;
var infowindow = new google.maps.InfoWindow();
for(var i=0; i < total; i++){
data_array=response[i];
name=data_array['name'];
id = data_array['id'];
address=data_array['address'];
arrival=data_array['arrival'];
departure=data_array['departure'];
notes=data_array['notes'];
lat=data_array['lat'];
lon=data_array['lon'];
icon=data_array['icon'];
sc_id=data_array['sc_id'];
var propPos = new google.maps.LatLng(lat,lon);
propMarker = new google.maps.Marker({
position: propPos,
map: map,
icon: icon,
zIndex: 3
});
var contentString = "<div style='font-size:9px;overflow:hidden'>"+name+"<br/><label class='label'>Location :</label> "+address+"<br/><label class='label'>Arrival :</label> "+arrival+"<br/><label class='label'>Departure :</label> "+departure+"<br/><label class='label'>Notes :</label> "+notes + "</div><div style='font-size:9px;overflow:hidden'><a href='#2' onclick="+xx+" class='popup-txt' style='font-size:11px; margin-top:3px;'>Message him</a><a href='#1' onclick="+invite+" class='popup-txt' style='font-size:11px; margin-top:3px; float:right;'>Invite Friend</a></div>";
function bindInfoWindow(marker, map, infowindow, html) {
google.maps.event.addListener(marker, 'click', function() {
infowindow.setContent(html);
infowindow.open(map, marker);
});
bindInfoWindow(propMarker, map, infowindow, contentString);
}
}
});
return;
}
and here is the marker.php mentioned in above js
<?php
$data=array();
$retrive_marker_query = "your query";
$result = db_execute($retrive_marker_query);
$cnt=0;
while ($row = mysql_fetch_assoc($result)){
$name = $row['name'];
$id = $row['fb_id'];
$sc_id = $row['id'];
$address = $row['location'];
$lat = $row['lat'];
$lon = $row['lon'];
$data[$cnt]['name'] = $name;
$data[$cnt]['id'] = $id;
$data[$cnt]['sc_id'] = $sc_id;
$data[$cnt]['address'] = $address;
$data[$cnt]['lat'] = $lat;
$data[$cnt]['lon'] = $lon;
$cnt++;
}
$data=json_encode($data);
echo($data);
<?

Categories

Resources