How I built an http://have-you-been-here.appointment.at/ using third party libraries from http://github.com/
http://have-you-been-here.appointment.at/ is a site where users may submit images of different locations on Earth (provided with geo-coordinates) so that other users may find nice places to visit. When building the site I had used many different PHP and JavaScript libraries. Here I'm sharing the information I leaned about these libs.
Composer
First of all, I use myself and recommend
composer. You list the libraries you need to use in your project and then say
composer update
and all listed libraries get installed into the
vendor/
folder. The same command is installing updates to the libs, if available. That's great - no need to check for updates manually. Below is part of my
composer.json
file. The rest of the post will shortly highlight each of these libraries.
{"name": "have-you-been-here",
"require": {
"components/jquery": "*",
"components/jqueryui": "*",
"components/bootstrap": "*",
"hybridauth/hybridauth": "dev-master",
"blueimp/jquery-file-upload": "dev-master",
"hpneo/gmaps": "dev-master",
"pear-pear.php.net/DB": "*",
"needim/noty": "dev-master",
"phayes/geoPHP": "dev-master",
"anthonymartin/geo-location": "dev-master",
"DanElliottPalmer/GeoJSON-Parser": "dev-master",
"jrburke/requirejs": "dev-master",
"flesler/jquery.scrollTo": "dev-master",
"bradwedell/php-google-map-api": "dev-master",
"ezyang/htmlpurifier": "dev-master",
"fg/essence": "dev-master",
"jakiestfu/Snap.js": "dev-master",
"components/modernizr": "dev-master"
}
}
jQuery
jQuery is a must in any project. It changes the way you will write JS. Your code would not look like plain JS anymore, but would become easier to read and shorter to write.
JQueryUI is a collection of widgets like a calendar, slider, ... and a nice CSS for these widgets and error messages.
Bootstrap
Even better CSS framework than jQueryUI. Just like nobody is writing pure JS without any libraries, you shouldn't write your own CSS from the scratch.
Hybridauth
Allows your users to login with Google, Twitter, Facebook and other accounts with OAuth. You can easily add any other OAuth provider which is not pre-configured in Hubridauth.
Jquery-file-upload
The best ever file upload lib with support for drag-n'-drop and unobtrusive degradation to standard HTML uploads. Accepting uploads in PHP is a little awkward (no data in
$_FILES
) but they provide a PHP class for handling uploads on the server.
Map libraries
I find the original Google Maps API too complicated for simple tasks like showing a map at the specified location and showing a single marker with a tooltip information.
php-google-map-api
Therefore I have gladly used a
php-google-map-api. Usage is pretty simple, her3 it shows a map of a specific location and a marker:
include_once("vendor/bradwedell/php-google-map-api/releases/3.0/src/GoogleMap.php");
include_once("vendor/bradwedell/php-google-map-api/releases/3.0/src/JSMin.php");
$map = new GoogleMapAPI();
$map->_minify_js = TRUE;
$map->width = '100%';
$map->addMarkerByCoords($coords[1], $coords[0], $this->model->getName());
$this->index->header[__METHOD__] = $map->getHeaderJS();
$this->index->header[__METHOD__] .= $map->getMapJS();
$content .= $map->getOnLoad();
$content .= '<div class="popin">'.$map->getMap().'</div>';
gmaps.js
This was fine for a static map, but once I needed an interaction with a user in the browser like repositioning the map after geolocation - I had to revert back to JS.
Luckily I've found a
gmaps.js a wrapper to Google Maps API with MUCH simplified API than one provided by Google. Making a map is as simple as this:
var map = new GMaps({
div: '#map',
lat: -12.043333,
lng: -77.028333
});
Adding a marker is so simple:
map.addMarker({
lat: -12.043333,
lng: -77.028333,
title: 'Lima',
click: function(e) {
alert('You clicked in this marker');
}
});
Geolocation in one line with four event handlers!
GMaps.geolocate({
success: function(position) {
map.setCenter(position.coords.latitude, position.coords.longitude);
},
error: function(error) {
alert('Geolocation failed: '+error.message);
},
not_supported: function() {
alert("Your browser does not support geolocation");
},
always: function() {
alert("Done!");
}
});
Definitely a way to go if you're new to maps.
GeoPHP and GeoJSON
Once you have a map you most likely need to display more than one marker and download and update markers according to the view area of the map (when user is panning or zooming). Surprisingly, Google doesn't provide any help in this. So everybody is inventing their own JSON/XML format for transferring marker information between server and JS. And, accordingly, a custom JS to parse and display markers.
There is one standard which kind of goes in the direction of standardizing the geo data transfer -
GeoJSON. It allows you to transfer points, polygons, filled areas. I was interested of transferring markers (points in GeoJSON) with additional information (title, tooltip text, click link). Unfortunately point data type is not meant for markers - all additional data are transferred as custom properties and the propery names are not standardized. But there's at least a PHP lib for generating GeoJSON and JS lib for parsing it.
Generation is done without any library like this:
$json = new stdClass();
$json->type = 'FeatureCollection';
$json->features = array();
foreach ($this->data as $row) {
$feature = new stdClass();
$feature->type = 'Feature';
$feature->id = $row['id'];
$feature->geometry = new stdClass();
$feature->geometry->type = 'Point';
$feature->geometry->coordinates = array($row['lon'], $row['lat']);
$properties = (object)$row;
unset($properties->exif);
$feature->properties = $properties;
$json->features[] = $feature;
}
echo json_encode($json);
AJAX requesting and parsing is done with
GeoJSON JS lib:
GeoJSON.loadJSON(jsonURL, function(e) {
var f = GeoJSON.parse(e);
for (var a = 0; a < f.length; a++) {
// add to the map
var latlng = f[a].getCenter();
if (!self.contains(latlng)) {
var marker = new google.maps.Marker({
position: latlng,
map: map.map,
title: f[a].properties.name
});
marker.place = f[a];
self.markers.push(marker);
}
}
});
As you can see, generation of the markers and reading marker properties is up to you to program.
Server side map support
I always thought (without having a clue) that one needs complicated queries for searching markers in the database. I've seen PostgreSQL even has a
plugin for geo support in the DB.
In fact - it's very simple. Google Maps gives you north-east and south-west coordinates of the currently displayed map. This makes your SQL as simple as
WHERE lat BETWEEN 10 and 20
AND lon BETWEEN 30 and 40
This, of course, requires that you have
lat
and
lon
in the database. If you need to geocode addresses into coordinates you cat use
Geocoder-PHP.
GeoLocation.php
There is a. lthough one situation where it gets more complicated. When geolocation has beed done in browser, you get lat/lon of the center of the map, but there is no predefined zoom level and hence - no map boundaries to use in the query above.
I decided to start searching for places in the 1 km area around the location. If there are no or less than three places found - I search in the 2, 4, 8, 16 etc. km area. To be able to search for markers inside the specified radius around the specified location requires converting location/radius into bounding-box coordinates. The math of the process is described nicely
here. A
GeoLocation.php library has a function for this.
function getBoundingBoxFrom(array $pos, $km) {
$edison = GeoLocation::fromDegrees($pos['lat'], $pos['lon']);
$coordinates = $edison->boundingCoordinates($km, 'kilometers');
$lat1 = $coordinates[0]->getLatitudeInDegrees();
$lon1 = $coordinates[0]->getLongitudeInDegrees();
$lat2 = $coordinates[1]->getLatitudeInDegrees();
$lon2 = $coordinates[1]->getLongitudeInDegrees();
return array($lat1, $lat2, $lon1, $lon2);
}
SQL will look like this:
list($lat1, $lat2, $lon1, $lon2) = $this->getBoundingBoxFrom($pos, $km);
$pc = new PlaceCollection(NULL, array(
'lat' => new SQLBetween($lat1, $lat2),
'lon' => new SQLBetween($lon1, $lon2),
));
Additional map operations
Autofitting map area to the set of found and diaplayed markers.
After retrieving some markers from the above query and transferring them to JS with GeoJSON one needs to zoom the map so all the markers are visible. Probably one could do the calculations in PHP and transfer that somehow in GeoJSON but this operation is easily done with standard Google Maps API.
You collext all markers in an array and get Boundary class do the work.
Reacting on user panning and zooming.
This is easily done qith standard API.
The event 'idle' is triggered after the map is done panning/zooming. Note that this event should be registered AFTER initial displaying of markers as we do boundary fitting, which triggers pan/zoom itself.
Sanitizing user comments
This is very important. Noone wants his site to be vulnerable to XSS or contain viruses. I thought PHP function 'strip_tags' is good enough for this. You are allowed to define unharmful tags which will be kept (like
<i>
,
<b>
). But is appears the function is not safe enough. See explanation on the page of ###. It also reviews other solutions and thwir shortcomings. Using ### is as simple as
Embedding content from user submitted links
OEmbwd stansard allows you to embed contend from other sites when all you have is a URL of the content to embed. This ia how you can have YouTube links to be shown as a player and Share? presentations as presentations. ### is the best library for this. Usage:
It comes with a list of oEmbes aites and you can add moee. Other libraries arw listed here, but I disn't like them as much as ###.
Slideable drawer
I believe this cocept is invented by Android or maybe Facebook app. Anyway, I fins it vwry convenient as a way to hide the sidwbar on the narrow screen devices. After reciewing suggested libraries at ###, I have choaen Snap. It requires a very specific HTML layout and uaes absolute CSS positioning so I had some work to embed it into Bootstrap layout, especially as I dont want any drawer functionality on the desktop beowsers. So I have a browser setection after the pagw ia loaded and rhen I manually adjuat Bootstrap layput to fit the requiremwnts of the Snap.
On mobile browser it looks like this.
Modernizr
See how I detect mobile browser in the coee above? Modernizr is testing different browser feautirws and als adds correspinding classes to the tag, allowing you