Johno's Place

Life and Times of the Johnstone Family

How to enable Draggable Markers for iPhone/iPad using the Nearmap API

We’re currently working on a solution for a client that makes use of Nearmap.com (under license) for up to date imagery of residential properties. Most users of the system will be accessing the site from iPads in the field while creating quotes for clients and the app involves placing and moving markers around the customer’s property image.

The problem is that in it’s current state, the Nearmap API does not support dragging markers when using maps from an iPad or iPhone. They do support moving the map around and pinch to zoom gestures, but not dragging.

To get around this issue we had to add some of our own event handlers to the Nearmap namespace. There were no specific functions for handling touch events on markers, but there are events for handling mouse clicks. So the process we went through was to duplicate the mouse events with touch events, and then make sure that they didn’t interfere with each other.

The trick is to make sure you’re registering the events at the right point and also making sure that you remove them once you’re done with the drag in touchend.

Here is the modifications we made in the hope that it might help someone else somewhere down the line (or at least a reminder to us!).

If you find something wrong or missing (which I’m almost sure there will be, the original file is 13,000 lines and I’m quickly trying to find what we did again) here let us know in the comments. But hopefully this will give you the general gist of what we did and help you along your way ;-)

Add new Marker functions:

Marker.prototype.onmarkertouchmove = function (e){
var map = this.map;
var dragged = this.moved;
var latlng;
var latlngPx;
var mapType;
var proj;
var tScale;
var heading;
var distMoved;
var sclDistMoved;
var rotatedDistMoved;
this.moved = true;

if (this.draggingEnabled()) {
if (e.touches.length == 1){
    var touch = e.touches[0];
    e = e || window.event;
    mapType = map.getCurrentMapType();
    proj = mapType.getProjection();
    heading = mapType.getHeading();
    tScale = mapType.getTileScale();
    latlng = this.getLatLng();
    latlngPx = proj.fromLatLngToPixel(latlng, map.getZoom(), mapType.getTileSize());
    distMoved = new nmm.Point(touch.clientX - this.clientX, touch.clientY - this.clientY);
    sclDistMoved = new nmm.Point(distMoved.x / tScale.x, distMoved.y / tScale.y);
    rotatedDistMoved = nmlm.rotatePoint(heading, sclDistMoved, nmm.Point.ORIGIN, Math.round);
    this.clientX = touch.clientX;
    this.clientY = touch.clientY;
    latlngPx.add(rotatedDistMoved.x, rotatedDistMoved.y);
    this.setLatLng(proj.fromPixelToLatLng(latlngPx, map.getZoom(), false, mapType.getTileSize()));
    Marker.prototype.moveIcons.call(this);

    if (!dragged) {
        this.foregroundImage.style.cursor = nmls.CURSOR_GRABBING.css;
        map.setDraggingCursor(nmls.CURSOR_GRABBING.css);
        Marker.prototype.renderPickup.call(this);
        nmev.trigger(this, "dragstart", this.getLatLng())
    }
    nmev.trigger(this, "drag", this.getLatLng());
    return nmev.preventDefault(e)
}
}
};

Marker.prototype.onwindowmarkertouchend = function (e){

            if (this.opts.draggable && this.moved){
                Marker.prototype.renderDrop.call(this);
                this.map.setDraggableCursor(nmls.CURSOR_GRAB.css);
                nmev.trigger(this, "dragend", this.getLatLng());
                 nmev.trigger(this, "mouseover", this.getLatLng());
                  nmev.trigger(this, "mouseout", this.getLatLng());
            }
            else{
                if (!this.moved) {
                    Marker.prototype.onmarkertouchstart.call(this, e)
                }
            }
            this.foregroundImage.style.cursor = "pointer";

            nmev.removeListener(this.onwindowmarkertouchend);
            nmev.removeListener(this.onmarkertouchmove);

            delete this.onwindowmarkertouchend;
            delete this.onmarkertouchmove;
            delete this.latlngPx;
            delete this.clientX;
            delete this.clientY;
            delete this.moved;
            return nmev.preventDefault(e)

    }

Marker.prototype.onmarkertouchstart = function (e) {
        e = e || window.event;
        if (e.touches.length == 1){
            var touch = e.touches[0];
            this.clientX = touch.clientX;
            this.clientY = touch.clientY;
            this.onmarkertouchmove = nmev.addDomListener(document, "touchmove", nmev.callback(this, Marker.prototype.onmarkertouchmove));
            this.onwindowmarkertouchend = nmev.addDomListener(document, "touchend", nmev.callback(this, Marker.prototype.onwindowmarkertouchend));
            nmev.trigger(this, "touchstart", this.getLatLng());
            nmev.stopBubbling(e);
            return nmev.preventDefault(e);
        }
    };

    Marker.prototype.onmarkertouchend = function (e) {
    nmev.trigger(this, "touchend", this.getLatLng());
    nmev.trigger(this, "mouseup", this.getLatLng()) ;
    nmev.trigger(this, "mouseover", this.getLatLng()) ;
    nmev.trigger(this, "mouseout", this.getLatLng()); };

    foregroundImage.ontouchstart = nmev.callback(this, Marker.prototype.onmarkertouchstart);
    foregroundImage.ontouchmove = nmev.callback(this, Marker.prototype.onmarkertouchmove);
    foregroundImage.ontouchend = nmev.callback(this, Marker.prototype.onmarkertouchend);

Add new DraggableObject functions:

DraggableObject.prototype.ontouchstart = function (e, dragObj) {
        var touch;
        if (dragObj._enabled && e.touches.length === 1) {
            touch = e.touches[0];
            dragObj.mouseX = touch.clientX;
            dragObj.mouseY = touch.clientY;
            dragObj.hasDragged = false;
            dragObj._ontouchmove = nmev.addDomListener(document, "touchmove", nmev.callbackArgs(document, DraggableObject.prototype.ontouchmove, dragObj));
            dragObj._ontouchend = nmev.addDomListener(document, "touchend", nmev.callbackArgs(document, DraggableObject.prototype.ontouchend, dragObj));
             dragObj._onmousemove = nmev.addDomListener(document, "mousemove", nmev.callbackArgs(document, DraggableObject.prototype.onmousemove, dragObj));
            dragObj._onmouseup = nmev.addDomListener(document, "mouseup", nmev.callbackArgs(document, DraggableObject.prototype.onmouseup, dragObj));
            if (window.parent.location.href !== window.location.href) {
                dragObj._onframetouchend = nmev.addDomListener(document, "touchend", nmev.callbackArgs(document, DraggableObject.prototype.onframemouseout, dragObj))
            }
            return nmev.preventDefault(e)
        }

    };
    DraggableObject.prototype.ontouchmove = function (e, dragObj) {
        var touch;
        var x;
        var y;
        var style;
        if (dragObj._enabled && e.touches.length === 1) {
            touch = e.touches[0];
            x = touch.clientX - dragObj.mouseX;
            y = touch.clientY - dragObj.mouseY;
            if (x !== 0 || y !== 0) {
                if (!dragObj.hasDragged) {
                    dragObj.hasDragged = true;
                    nmev.trigger(dragObj, "dragstart", touch)
                }
                dragObj.moveBy(new nmm.Size(x, y));

                dragObj.mouseX = touch.clientX;
                dragObj.mouseY = touch.clientY;
                nmev.trigger(dragObj, "drag", touch)
            }
        }

        return nmev.preventDefault(e)
    };

    DraggableObject.prototype.ontouchend = function (e, dragObj) {
        var hasDragged = dragObj.hasDragged;
        var touch;
            hasDragged = dragObj.hasDragged;
            delete dragObj.mouseX;
            delete dragObj.mouseY;
            delete dragObj.hasDragged;
                touch = e.touches[0];
                dragObj.dragend(touch)

    };

Modify existing DraggableObject functions:

function DraggableObject(src, opts) {
        var style = src.style;
        this._src = src;
        this._enabled = true;
        opts = nmo.Synchronize.fill(opts, DraggableObjectOptions);
        if (opts.draggableCursor) {
            this._draggableCursor = opts.draggableCursor
        }
        if (opts.draggingCursor) {
            this._draggingCursor = opts.draggingCursor
        }
        if (opts.left) {
            this._left = opts.left
        }
        else {
            if (!style.left) {
                this._left = 0
            }
            else {
                this._left = parseInt(style.left, 10)
            }
        }
        if (opts.top) {
            this._top = opts.top
        }
        else {
            if (!style.top) {
                this._top = 0
            }
            else {
                this._top = parseInt(style.top, 10)
            }
        }
        style.cursor = this._draggingCursor;
        style.cursor = this._draggableCursor;
        if (style.position !== "relative" || style.position !== "absolute") {
            style.position = "absolute"
        }
        style.left = this._left + "px";
        style.top = this._top + "px";
        this._onmousedown = nmev.addDomListener(src, "mousedown", nmev.callbackArgs(src, DraggableObject.prototype.onmousedown, this));
        this._ontouchstart = nmev.addDomListener(src, "touchstart", nmev.callbackArgs(src, DraggableObject.prototype.ontouchstart, this));
    }

DraggableObject.prototype.dragend = function (e) {
        var hasDragged = this.hasDragged;
        if (this._onmousemove)
        {
            nmev.removeListener(this._onmousemove);
        }
        if (this._onmouseup)
        {
            nmev.removeListener(this._onmouseup);
        }
        if (this._ontouchmove)
        {
            nmev.removeListener(this._ontouchmove);
        }
        if (this._ontouchend)
        {
            nmev.removeListener(this._ontouchend);
        }
        if (this._onframemouseout) {
            nmev.removeListener(this._onframemouseout)
        }
        delete this._onmousemove;
        delete this._onmouseup;
        delete this._ontouchmove;
        delete this._ontouchend;
        delete this._onframemouseout;
        delete this.mouseX;
        delete this.mouseY;
        delete this.hasDragged;
        if (this._enabled) {
            this._src.style.cursor = this._draggableCursor;
            if (hasDragged) {
                nmev.trigger(this, "dragend", e)
            }
            nmev.trigger(this, "mouseup", e);
            nmev.trigger(this, "click", e);
        }
    };

Ethan 14 Months

image

My boy enjoying his blocks. 14 months old

Software Engineers are Creators, Not Builders

rogrammer at work. Code monkey is working late. #newsfromthecubeI read a great article about the psychology  of software developers this morning. It rang true with my experiences of working as a developer for the past decade. Nicholas describes very well what many people outside of the profession fail to understand: software development is creating, not building.

Software engineers are creators. Building is what you do when you buy a piece of furniture from Ikea and get it home. The instructions are laid out and if you go step by step, you’ll get that comically small table you wanted. Creating is a different process, it’s birthing something without direction or instruction. It’s starting with a blank canvas and painting a masterpiece.

It’s a long article that is worth a read when you have the time if your job involves working with software developers, or if someone in your family is a software developer. The reaction this article is getting is that it’s sentiment rings true for a large number of people.

So bookmark/instapaper the link and settle in for a read this weekend, and thanks Nicholas for writing so clearly what many of us are thinking and feeling.

My New Project

I’ve got a new project. I’ve been looking for something new to do lately and somehow I came up with this:

Aussie Geek Dad

I guess it an outlet for what I’m going through at the moment helping raise Ethan. They say if you’re going to write about something make it something you know, and I guess I know a little bit about it now. Not saying I’m an expert, but we learn by talking as well as doing.

It’s a bit rough at the moment, but it should improve over time as I learn what works and what doesn’t work. It should be an interesting journey at least ;-)

Growing Up So Fast

Ethan just keeps growing! He has started commando crawling everywhere now, using his hands and big toes to get everywhere.

First Laugh

It’s the small things in life that make it all worth it. Here is one of them:

Tummy Time

IMG_5106

And if you look closely, you can see his first two teeth on the bottom!

Ethan’s first tooth

Tonight while he was in the bath I noticed a bit more white in his mouth than I remembered. We had a closer look and sure enough there was a tooth just breaking through the surface of the gum.

The funny thing is that we hadn’t really noticed too much discomfort. I had feeling that it wasn’t far away when he was sucking on my finger the other night.

Hopefully all his teeth. Come through without too much pain. I have a feeling he has his mother’s pain threashhold.

I’ve been busy

Wow, I have been so busy since those floors went in I haven’t had time to upload any photos of Ethan (the reason for all they busy-ness) here! Most people will have seen them on Facebook, but I do like to put stuff outside the walled garden every now and then Winking smile

This is my favourite. The photographer said she usually Photoshop’s this type of photo with a composite of upper and lower images where the parents hold the chin and forehead in each, but Stacey just let go and he held this pose all by himself (not even 2 weeks old at this point!)

IMG_7693

Another classic pose here which helps you get a perspective on how small they start out!

IMG_7857-

I like this one as it shows off our new floors with our new bub Winking smile

IMG_7863

A bit thanks to K Etherington for these photos, I’ll recommend her to anyone with a newborn, she’s a real pro!

If I get a chance I’ll put up some of the photos we’ve taken of him soon (there are about 1500 already I think!)

Floors Finished!

The final work on our new Marri floors was finished yesterday!

The end result looks great! We’re really happy with the whole outcome, from the type of wood to the quality of the finish.

Here are some pictures I took through the windows last night (won’t go in there until at least tomorrow to allow it to properly dry):

Living Room:

IMG_2294

Games Room/Dining:

IMG_2295

Games Room:

IMG_2297

Lounge Room:

IMG_2303

Post Navigation

Follow

Get every new post delivered to your Inbox.