Wednesday, 26 February 2014

Markers, popups and labels with leaflet and openstreetmap

It took me a while to get this code working right, and now I have I thought I'd share/store it. It could probably do with a little refactoring by outsourcing some of it to other methods, but it's good to go and its linear nature makes it easier to post here. I started this off using google maps but the performance was terrible and after a bit of zooming/panning the app would crash when running on a device. So I opted for leaflet with openstreetmap and I'm really please with the results.

What I'm doing with this code is plotting 40 or so holiday parks with markers and labels, with an associated popup when clicked. In the popup is a button (a JQM widget) that performs an action. Further to this I wanted to switch the marker for a larger one and hide/show labels at a certain zoom level, and I also wanted to specify different label orientations (top,right,left,bottom) for each park.

The end result looks something like this:

              

mapPagePopulate:function(){

                var self=this;
                self.show_loader("Loading map data...");

                //set initial zoom depending on device
                var initZoom = 7;
                if(self.deviceIsMobile()){ initZoom = 6; }

                //init map with centre around Nottingham
                var map = L.map('map_canvas').setView([52.946104,-1.170044], initZoom);
              
                //add copyright - v important!
                L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
                    attribution: 'Map data © OpenStreetMap contributors',
                    maxZoom: 18,
                    minZoom: 6
                }).addTo(map);


                //get park data from local storage
                    var parkListLite = $.parseJSON(window.localStorage.getItem(self.localStorageKeys.parkListLite));
                   
                   //declare empty arrays
                    var locations = [];
                    var markers = [];

                    //get label positiong (t,r,b,l)
                    var labelPositions = self.settings.mapLabelPositions();
   
                    //loop through park data, create location object with everything we need,
                    //and push it to locations array
                    $(parkListLite).each(function(i, obj){ 
                        var location = [];

                        //popup html
                        var calloutMarkup = String.format('

{0}

'+ '{1} '+ 'From {4}', obj['ParkName'], obj['MapCopy'], obj['EPiServerPageId'], self.endpoints.img_url + obj['MapThumbnail'], self.utils.makePriceFriendly(obj['CheapestPrice'],"£") ); var wrapper = document.createElement("div"); wrapper.innerHTML = calloutMarkup; wrapper.className="mapCallout"; location[0] = obj['ParkName']; location[1] = obj['Latitude']; location[2] = obj['Longitude']; location[3] = wrapper; location[4] = labelPositions[obj['ParkName']]; location[5] = ''+ obj['ParkName'] +''; locations.push(location); }); //create icons var mapMarkerSmall, mapMarkerLarge, iconAnchor; $(locations).each(function(i, loc){ //label anchor influences where the popup appears //here we check if we want top, left, right or bottom //and specify the anchor and css accordingly switch(loc[4]) { case 't': labelAnchor= [-128,-63]; labelClass = "mapLabel top"; break; case 'r': labelAnchor= [-263,-24]; labelClass = "mapLabel right"; break; case 'b': labelAnchor= [-128,25]; labelClass = "mapLabel bottom"; break; case 'l': labelAnchor= [0,-24]; labelClass = "mapLabel left"; break; default: labelAnchor= [0,-24]; labelClass = "mapLabel left"; } //create the large and small icon for each park, with the anchor specified above mapMarkerSmall = L.icon({ iconUrl: 'img/mapParkMarker.png', iconRetinaUrl: 'img/mapParkMarker.png', iconSize: [20, 20], iconAnchor: [10,10], popupAnchor: [0,-20], labelAnchor: labelAnchor }); mapMarkerLarge = L.icon({ iconUrl: 'img/mapMarkerLarge.png', iconRetinaUrl: 'img/mapMarkerLarge.png', iconSize: [130, 130], iconAnchor: [65,65], popupAnchor: [0,-20], labelAnchor: labelAnchor }); //add marker to map with label var marker = L.marker([loc[1], loc[2]], {icon: mapMarkerSmall}).bindLabel(loc[5], {noHide: true,direction: 'left',className:labelClass}).addTo(map); //bind popup to marker marker.bindPopup(loc[3]); //we can assign arbitrary properties to marker. here we assign small and large markers marker.iconSmall = mapMarkerSmall; marker.iconLarge = mapMarkerLarge; //hide labels on initial load map.removeLayer(marker.label); //push marker to marker array markers.push(marker); }); //for some zooms we need to show/hide labels or switch out markers for larger/smaller map.on('zoomend', function(event) { var zoom = map.getZoom(); var markerLabelClass; for (i = 0; i < locations.length; i++) { markerLabelClass = markers[i].labelClass; if(zoom <= 10){ markers[i].setIcon(markers[i].iconSmall); map.removeLayer(markers[i].label); }else{ markers[i].setIcon(markers[i].iconLarge); map.addLayer(markers[i].label); } } }); //capture popup open event and run JQM create so it creates the button widget //also assign click event to park view button map.on('popupopen', function(e) { $('#map_canvas').trigger('create'); $('.parkButton').unbind(); $('.parkButton').on('click',function(){ self.setLocalStorage(self.localStorageKeys.lastRequestedParkId,$(this).data('parkPageId')); self.utils.notify('Not yet implemented','OK'); //$.mobile.changePage('#parkoverview'); }); }); self.hide_loader(); }

SkyDrive needs to be updated AppStoreRequiredUpdateAva'ilableMessage

So, it looks like Microsoft have lazily given us a painful upgrade path now that they've been forced to rename SkyDrive. This morning I logged into my Mac and got this bizarre message:



At first I thought this may be some kind of virus duping me into handing over my SkyDrive detail, and there's no mention of this update in the app store, presumably because the SkyDrive app no longer exists, and it's not an update at all, more a replacement.

They've switched off SkyDrive and done a runner; The 'Get the Update' button takes you to the appstore where you can download OneDrive, and sure enough you have to reconfigure your SkyDrive settings (folder, preferences) all over again, just like you were a new user. As I type, my new OneDrive installation is slowly synching with the server. It was all bang up-to-date yesterday at 5pm but it looks like it's going through every single file to work out what yesterday it already knew.

And no doubt I'll have to go through the same ball-ache on my other devices too. Grrr.

Friday, 21 February 2014

Changing the name of a Phonegap project

This is always a pain, but these instructions found on StackOverflow seem to hit the spot:
  • Edit the file ./.cordova/config.json Change the "name" field to your new project name. 
  • Edit the file ./www/config.xml Change the "name" field to your new project name. 
  • Make a copy of your ./platforms/ios directory (optional, only needed if you have modified the ios code directly) 
  • Remove the ./platforms/ios directory. 
  • Run "phonegap build ios" This will create a new project with the correct name.

Thursday, 6 February 2014

Safari text input fields and -webkit-user-select: none;

I've been using this in my css to prevent selection of text with long presses:
/* remove touch callout - visible when pressing and holding a link */
* {
-webkit-touch-callout: none;
-webkit-user-select: none;
}
But I discovered today that in Safari only (including iPhone) this actually prevents the input/edit of text in an text input box. The fix is to override the css for inputs like so:
input {
    -webkit-user-select: auto !important;
}