Sunday, September 13, 2015

About Mapping Services and Other PHP Libraries for using in Mashup application

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

1 comment:

Unknown said...

As claimed by Stanford Medical, It's indeed the SINGLE reason this country's women live 10 years more and weigh on average 19 kilos lighter than us.

(By the way, it has totally NOTHING to do with genetics or some secret-exercise and EVERYTHING around "HOW" they eat.)

P.S, What I said is "HOW", and not "what"...

Tap this link to determine if this brief test can help you unlock your real weight loss potential