Source: code.js


var position = [48.210033, 16.363449];
var venues  = [];
// the html div element "mydiv" saves the response from foursquare
// if no request is made, just show the map.
if(document.getElementById("mydiv").innerText.length > 2 ) {
var geocode = JSON.parse(document.getElementById("mydiv").innerText);
var geoposition = geocode.response.geocode.feature.geometry.center;

position = [geoposition.lat, geoposition.lng];
venues = geocode.response.venues;
}


var mymap = L.map(document.getElementById("mapid"), {minZoom: 4,
    maxZoom: 20}).setView(position, 13);
//mapbox own key
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFzdGVyY29ycCIsImEiOiJjamkxdHlpM3IwejY3M3VybjFhdHV4OWZrIn0.K9IWyiRWAu9zEwNalyYpog', {
    maxZoom: 18,
    attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, ' +
    '<a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, ' +
    'Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
    id: 'mapbox.streets'
}).addTo(mymap);

// helper var for marker deletion later
var marker;

start();

// if the map is moved or zoomed in or out, delete markers and create everything new
mymap.on('moveend', function(e) {
    markerDelAgain();
    start();
});

/**Initialize all found elements and create the markers with corrected order and no overlap */
function start() {
    marker = new Array();
    var elements = [];
    for (var i = 0; i < venues.length; i++) {
        pixelpos = mymap.project([venues[i].location.lat, venues[i].location.lng]);
        xcenter = pixelpos.x;
        ycenter = pixelpos.y;
        width = 150;
        height = 100;
        xrank = xcenter;
        yrank = ycenter;
        name = venues[i].name;
        adress = venues[i].location.formattedAddress;

        var element = {
            x: xcenter - width/2, y: ycenter - height/2, width: width, height: height,
            index: i + 1, xcenter: xcenter, ycenter: ycenter,
            xrank: xrank, yrank: yrank, name:name, adress: adress
        };
        elements.push(element)
    }


// elements sorted by index
    elements.sort(function (a, b) {
        return a.index - b.index
    });

    var overlapRect = detectOverlap(elements);

    // while there are elements overlapping, remove the overlap and repair their order
    while (overlapRect != 0) {
        while (overlapRect.length > 0) {
            var overLap = overlapRect.pop();
            removeOverlap(overLap[0], overLap[1]);
        }

        elements.sort(function (a, b) {
            return (a.xcenter - b.xcenter)});
        // repair order for x direction
        elements = orderRepair(elements, 0);

        elements.sort(function (a, b) {
            return (a.ycenter - b.ycenter)});
        // repair order for y direction
        elements = orderRepair(elements, 1);

        overlapRect = detectOverlap(elements);


    }

    // sort elements by index and create the markers for the map
    elements.sort(function (a, b) {
        return a.index - b.index
    });

    for(var i= 0; i < elements.length; i++){
         var latlng = mymap.unproject([elements[i].x, elements[i].y]);
         marker.push(L.marker(new L.LatLng(latlng.lat, latlng.lng), {icon:createLabelIcon("textLabelclass", elements[i])}));
    }

    // add all markers to an array => easy to delete after
    for(var index = 0; index < marker.length; index++){
        marker[index].addTo(mymap);
    }


}
/**
 * removes overlap of 2 elements in the shortest overlap direction
 * @param {element} rect1 and rect2 are 2 elements
 * @param {element} rect1 and rect2 are 2 elements
 */
function removeOverlap(rect1, rect2){
    var xoverlap;
    var yoverlap;
    var threshold = 2;
    if (rect1.xrank <  rect2.xrank){
        xoverlap = (rect1.x + rect1.width) - rect2.x;
    }
    else if (rect1.xrank >  rect2.xrank){
        xoverlap = (rect2.x + rect2.width) - rect1.x;
    } else{
        xoverlap = Number.MAX_SAFE_INTEGER;
    }

    if (rect1.yrank <  rect2.yrank){
        yoverlap = (rect1.y + rect1.height) - rect2.y;
    }
    else if (rect1.yrank >  rect2.yrank){
        yoverlap = (rect2.y + rect2.height) - rect1.y;
    } else{
        yoverlap = Number.MAX_SAFE_INTEGER;
    }
    if (xoverlap < threshold){
        xoverlap = threshold;
    }
    if(yoverlap < threshold){
        yoverlap = threshold;
    }
    if(xoverlap <= yoverlap) {
        if(rect1.xrank < rect2.xrank) {
            rect1.x = rect1.x - xoverlap / 2;
            rect2.x = rect2.x + xoverlap / 2 ;
        }else if(rect1.xrank > rect2.xrank){
            rect1.x = rect1.x + xoverlap / 2 ;
            rect2.x = rect2.x - xoverlap / 2 ;
        }
        rect1.xcenter = calcCenter(rect1.x, rect1.width);
        rect2.xcenter = calcCenter(rect2.x, rect2.width);
    }
 else if ( xoverlap > yoverlap){
        if(rect1.yrank < rect2.yrank) {
            rect1.y = rect1.y - yoverlap / 2 ;
            rect2.y = rect2.y + yoverlap / 2 ;
        }else if(rect1.yrank > rect2.yrank) {
            rect1.y = rect1.y + yoverlap / 2 ;
            rect2.y = rect2.y - yoverlap / 2 ;
        }
        rect1.ycenter = calcCenter(rect1.y, rect1.height);
        rect2.ycenter = calcCenter(rect2.y, rect2.height);
    }

}

/**
 * Tests if 2 rectangles are overlapping, returns true or false
 * @param {element} rect1 and rect2 are 2 elements
 * @param {element} rect1 and rect2 are 2 elements
 * @return {boolean} true if overlapping, false else
 */
function isOverlapping(rect1, rect2) {
    if(rect1.x >  (rect2.x + rect2.width) ||
        (rect1.x + rect1.width) < rect2.x ||
        rect1.y > (rect2.y + rect2.height) ||
        (rect1.y + rect1.height) < rect2.y ) {
        return false;
    } else{
        return true;
    }
}

/**
 * detects overlapping elements and returns a list with overlapping arrays.
 *
 * @param {Object} elements - list of elements
 * @return {Array} returns multidimensional array of overlapping elements.
 */
function detectOverlap(elements){
var overlapRect = [];
for(var i = 0; i < elements.length -1 ; i ++) {
    for (var k = i + 1; k < elements.length; k++) {
        if (isOverlapping(elements[i], elements[k])) {
            overlapRect.push([elements[i], elements[k]]);
        }
    }
}
    return overlapRect;
}


/**
 * Modified version of MERGE-SORT. removeOverlap can introduce orthogonal order errors.
 * orderRepair pushes elements with a wrong orthogonal order on the same spot. ( seperate for x and y direction )
 * After that, removeOverlap can be applied again to the new overlapping elements.
 *
 * @param {Object} elements - list of elements
 * @param {boolean} dimension - 0 for x dimension, 1 for y dimension
 * @return {Array<Object>} returns Array of elements with new overlaps
 */
function orderRepair(elements, dimension){
    if( elements.length == 1){
        return elements;
    }
    var mid = Math.floor(elements.length/2);
    var leftlist = elements.slice(0, mid);
    var rightlist = elements.slice(mid);
    return merge(orderRepair(leftlist, dimension),orderRepair(rightlist,dimension), dimension);
}

/**
 * merges lists of elements together

 *
 * @param {Object} left - list of elements
 * @param {Object] right - list of elements
 * @param {boolean} dimension - 0 for x dimension, 1 for y dimension
 * @return {Array<Object>} returns Array of elements with new overlaps
 */
function merge(left, right, dimension){
    var result = [];
    var indexleft = 0;
    var indexright = 0;

    while(indexleft < left.length && indexright < right.length && dimension == 0){
        if( left[indexleft].xrank < right[indexright].xrank){
            result.push(left[indexleft]);
            indexleft++;
        }else {
            var group = [];
            for(var index = indexleft; index < left.length; index++) {
                group.push(left[index]);
            }
            for(var index = indexright; index < right.length; index++) {
                                if(left[indexleft].xrank > right[index].xrank){
                     group.push(right[index]);
                     break;
                }else if(left[indexleft].xrank == right[index].xrank){
                    group.push(right[index]);
                }}
                var xavg = 0;
                for(var index = 0; index < group.length; index++){
                    xavg = xavg + group[index].xcenter;
                }
                xavg = xavg / group.length;

                for(var index = 0; index < group.length; index++){
                    group[index].xcenter = xavg;
                    group[index].x = calcBorder(xavg, group[index].width);
                }

                result.push(right[indexright]);
                indexright++;
            }

        }


    while(indexleft < left.length && indexright < right.length && dimension == 1){
        if( left[indexleft].yrank < right[indexright].yrank){
            result.push(left[indexleft]);
            indexleft++;
        } else {
            var group = [];

            for(var index = indexleft; index < left.length; index++) {
                group.push(left[index]);
            }


            for(var index = indexright; index < right.length; index++) {
                if(left[indexleft].yrank > right[index].yrank){
                     group.push(right[index]);
                     break;
                }
                else if(left[indexleft].yrank == right[index].yrank){
                    group.push(right[index]);
            }}

            var yavg = 0;
            for(var index = 0; index < group.length; index ++){
                yavg = yavg + group[index].ycenter;
            }
                yavg = yavg / group.length;

            for(var index = 0; index < group.length; index ++){
                group[index].ycenter = yavg;
                group[index].y = calcBorder(yavg, group[index].height);
            }

            result.push(right[indexright]);
            indexright++;
        }

    }
    return result.concat(left.slice(indexleft)).concat(right.slice(indexright));
}

/**
 * calculate xcenter or ycenter pixel from pixel and height/width
 *
 * @param {float} center - xcenter or ycenter pixel from element
 * @param {float} length - width or height from element
 * @return {float} returns centerpixel
 */
function calcCenter(position, length){
    return position + length/2;
}

/**
 * calculate x or y pixel from center and height/width
 *
 * @param {float} center - xcenter or ycenter pixel from element
 * @param {float} length - width or height from element
 * @return {float} returns borderpixel
 */
function calcBorder(center, length){
    return center - length/2;
}

/**
 * Removes all Markers from the map. Needed for different Zoom levels.
 */
function markerDelAgain() {
    for(var i=0;i<marker.length;i++) {
        mymap.removeLayer(marker[i]);
    }
}
/**
 * Create the Location Labels as custom div markers instead of an image.
 * height and width is hardcoded into the div.
 * @param {string} labelClass - custom classname for divIcon
 * @param {Object} element - custom element with location-name and location-adress
 * @return {divIcon} returns icon
 */
function createLabelIcon(labelClass, element){
  return L.divIcon({
    className: labelClass,
    html: "<div style='color:#AACCFF; text-align: center; vertical-align: middle;  border-radius: 10px; " +
    "  word-wrap: break-word; display: inline-block;  height:100px; width:150px; background-color:#222233'>"+
    "<b>"+element.name+"</b>" + "<br>".concat(element.adress[0], "<br>", element.adress[1], "<br>",
        element.adress[2])+"</div>"
  })
}