Skip to content

Instantly share code, notes, and snippets.

@fxi
Last active May 2, 2017 14:17
Show Gist options
  • Select an option

  • Save fxi/b7f1af5981432296bfafec70a95fd9b6 to your computer and use it in GitHub Desktop.

Select an option

Save fxi/b7f1af5981432296bfafec70a95fd9b6 to your computer and use it in GitHub Desktop.

Revisions

  1. fxi revised this gist Sep 1, 2016. 2 changed files with 25 additions and 10 deletions.
    9 changes: 9 additions & 0 deletions app.js
    Original file line number Diff line number Diff line change
    @@ -117,8 +117,17 @@ if (window.File && window.FileReader && window.FileList && window.Blob) {
    }

    };

    // launch process
    try {
    w.postMessage(res);
    }catch(err){
    alert("An error occured, quick ! check the console !");
    console.log({
    res : res,
    err : err
    });
    }
    };
    };

    26 changes: 16 additions & 10 deletions handleReadJson.js
    Original file line number Diff line number Diff line change
    @@ -50,7 +50,6 @@ onmessage = function(e) {
    // start timer
    timerStart();


    /**
    * validation : geojson validation with geojsonhint
    */
    @@ -145,14 +144,19 @@ onmessage = function(e) {
    * Avoid multi type : we don't handle them for now
    */

    // array of types in data
    var geomTypes = gJson.features
    .map(function(x){
    return typeSwitcher[x.geometry.type];
    })
    .filter(function(v,i,s){
    return s.indexOf(v) === i;
    });
    var geomType = [];
    if( gJson.features ){
    // array of types in data
    geomTypes = gJson.features
    .map(function(x){
    return typeSwitcher[x.geometry.type];
    })
    .filter(function(v,i,s){
    return s.indexOf(v) === i;
    });
    }else{
    geomTypes = [typeSwitcher[gJson.geometry.type]];
    }


    postMessage({
    @@ -230,10 +234,12 @@ onmessage = function(e) {
    layer: dummyStyle[typ]
    });
    }

    catch(err) {
    console.log(err);
    postMessage({
    progress: 100,
    errorMessage : err
    errorMessage : "An error occured, yey ! Quick, check the console !"
    });
    }
    };
  2. fxi revised this gist Sep 1, 2016. 2 changed files with 3 additions and 3 deletions.
    2 changes: 1 addition & 1 deletion app.js
    Original file line number Diff line number Diff line change
    @@ -76,7 +76,7 @@ if (window.File && window.FileReader && window.FileList && window.Blob) {
    true,
    theFile.name,
    m.progress,
    theFile.name + ":" + m.message
    theFile.name + ": " + m.message
    );
    }

    4 changes: 2 additions & 2 deletions handleReadJson.js
    Original file line number Diff line number Diff line change
    @@ -148,7 +148,7 @@ onmessage = function(e) {
    // array of types in data
    var geomTypes = gJson.features
    .map(function(x){
    return x.geometry.type;
    return typeSwitcher[x.geometry.type];
    })
    .filter(function(v,i,s){
    return s.indexOf(v) === i;
    @@ -189,7 +189,7 @@ onmessage = function(e) {
    var colB = randomHsl(0.5, ran);

    // Set default type from geojson type
    var typ = typeSwitcher[geomTypes[0]];
    var typ = geomTypes[0];

    // Set up default style
    var dummyStyle = {
  3. fxi revised this gist Sep 1, 2016. 1 changed file with 2 additions and 1 deletion.
    3 changes: 2 additions & 1 deletion app.js
    Original file line number Diff line number Diff line change
    @@ -141,7 +141,8 @@ if (window.File && window.FileReader && window.FileList && window.Blob) {
    f = files[i];

    // Only process geojson files. Validate later.
    if (f.name.indexOf(".geojson") == -1) {
    if (f.name.toLowerCase().indexOf(".geojson") == -1) {
    alert(f.name + " not supported");
    continue;
    }
    // get a new reader
  4. fxi revised this gist Sep 1, 2016. 1 changed file with 0 additions and 1 deletion.
    1 change: 0 additions & 1 deletion README.md
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,3 @@
    # Drop multiple large geojson in mapbox gl js

    Experimental work with mapbox gl js, web workers and geojsonhint to create new layers by drag and dropping geojson data.

  5. fxi revised this gist Sep 1, 2016. No changes.
  6. fxi revised this gist Sep 1, 2016. 1 changed file with 4 additions and 0 deletions.
    4 changes: 4 additions & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,4 @@
    # Drop multiple large geojson in mapbox gl js

    Experimental work with mapbox gl js, web workers and geojsonhint to create new layers by drag and dropping geojson data.

  7. fxi revised this gist Sep 1, 2016. 2 changed files with 10 additions and 12 deletions.
    14 changes: 6 additions & 8 deletions app.js
    Original file line number Diff line number Diff line change
    @@ -2,16 +2,14 @@
    mapboxgl.accessToken = 'pk.eyJ1IjoidW5lcGdyaWQiLCJhIjoiY2lzZnowenUwMDAzdjJubzZyZ3R1bjIzZyJ9.uyP-RWjY-94qCVajU0u8KA';
    var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v9',
    center: [15.6, -4.3],
    zoom: 10
    style: 'mapbox://styles/mapbox/dark-v9',
    center: [-14.66,-23.64],
    zoom: 3
    });


    // object to hold geojson
    var data = {};



    // test if file api is available
    if (window.File && window.FileReader && window.FileList && window.Blob) {

    @@ -59,7 +57,7 @@ if (window.File && window.FileReader && window.FileList && window.Blob) {
    var startWorker = function(theFile) {
    return function(e) {
    // Create a worker to handle this file
    var w = new Worker("js/handleReadJson.js");
    var w = new Worker("handleReadJson.js");

    // parse file content before passing to worker.
    var gJson = JSON.parse(e.target.result);
    @@ -107,7 +105,7 @@ if (window.File && window.FileReader && window.FileList && window.Blob) {
    "data": gJson
    });
    // add layer
    map.addLayer(m.layer);
    map.addLayer(m.layer,"place-village");
    // set progress to max
    data[m.id] = gJson;
    }
    8 changes: 4 additions & 4 deletions handleReadJson.js
    Original file line number Diff line number Diff line change
    @@ -68,8 +68,8 @@ onmessage = function(e) {

    // set a message with summary
    var logMessage = " geojson validation " +
    " n errors=" + errors.length +
    " n warnings =" + warnings.length + " done in" +
    " n errors = " + errors.length +
    " n warnings = " + warnings.length + " done in" +
    timerLapString();

    console.log(fileName + ":" + logMessage);
    @@ -185,8 +185,8 @@ onmessage = function(e) {
    var id = "mgl_drop_" + randomString(5) + "_" + fileName ;
    // Set random color
    var ran = Math.random();
    var colA = randomHsl(0.6, ran);
    var colB = randomHsl(0.8, ran);
    var colA = randomHsl(0.1, ran);
    var colB = randomHsl(0.5, ran);

    // Set default type from geojson type
    var typ = typeSwitcher[geomTypes[0]];
  8. fxi revised this gist Sep 1, 2016. No changes.
  9. fxi revised this gist Sep 1, 2016. 1 changed file with 4 additions and 4 deletions.
    8 changes: 4 additions & 4 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -6,13 +6,13 @@
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <meta http-equiv="cache-control" content="Public">
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.23.0/mapbox-gl.css' rel='stylesheet' />
    <link href='css/progress.css' rel='stylesheet' />
    <link href='css/app.css' rel='stylesheet' />
    <link href='progress.css' rel='stylesheet' />
    <link href='app.css' rel='stylesheet' />
    </head>
    <body>
    <div id='map'></div>
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.23.0/mapbox-gl.js'></script>
    <script src='js/helpers.js'></script>
    <script src='js/app.js'></script>
    <script src='helpers.js'></script>
    <script src='app.js'></script>
    </body>
    </html>
  10. fxi revised this gist Sep 1, 2016. 9 changed files with 0 additions and 2266 deletions.
    File renamed without changes.
    File renamed without changes.
    268 changes: 0 additions & 268 deletions css/mapbox-gl.css
    Original file line number Diff line number Diff line change
    @@ -1,268 +0,0 @@
    .mapboxgl-map {
    font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
    overflow: hidden;
    position: relative;
    -webkit-tap-highlight-color: rgba(0,0,0,0);
    }

    .mapboxgl-canvas-container.mapboxgl-interactive,
    .mapboxgl-ctrl-nav-compass {
    cursor: -webkit-grab;
    cursor: -moz-grab;
    cursor: grab;
    }
    .mapboxgl-canvas-container.mapboxgl-interactive:active,
    .mapboxgl-ctrl-nav-compass:active {
    cursor: -webkit-grabbing;
    cursor: -moz-grabbing;
    cursor: grabbing;
    }

    .mapboxgl-ctrl-top-left,
    .mapboxgl-ctrl-top-right,
    .mapboxgl-ctrl-bottom-left,
    .mapboxgl-ctrl-bottom-right { position:absolute; pointer-events:none; z-index:2; }
    .mapboxgl-ctrl-top-left { top:0; left:0; }
    .mapboxgl-ctrl-top-right { top:0; right:0; }
    .mapboxgl-ctrl-bottom-left { bottom:0; left:0; }
    .mapboxgl-ctrl-bottom-right { right:0; bottom:0; }

    .mapboxgl-ctrl { clear:both; pointer-events:auto }
    .mapboxgl-ctrl-top-left .mapboxgl-ctrl { margin:10px 0 0 10px; float:left; }
    .mapboxgl-ctrl-top-right .mapboxgl-ctrl{ margin:10px 10px 0 0; float:right; }
    .mapboxgl-ctrl-bottom-left .mapboxgl-ctrl { margin:0 0 10px 10px; float:left; }
    .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl { margin:0 10px 10px 0; float:right; }

    .mapboxgl-ctrl-group {
    border-radius: 4px;
    -moz-box-shadow: 0px 0px 2px rgba(0,0,0,0.1);
    -webkit-box-shadow: 0px 0px 2px rgba(0,0,0,0.1);
    box-shadow: 0px 0px 0px 2px rgba(0,0,0,0.1);
    overflow: hidden;
    background: #fff;
    }
    .mapboxgl-ctrl-group > button {
    width: 30px;
    height: 30px;
    display: block;
    padding: 0;
    outline: none;
    border: none;
    border-bottom: 1px solid #ddd;
    box-sizing: border-box;
    background-color: rgba(0,0,0,0);
    cursor: pointer;
    }
    /* https://bugzilla.mozilla.org/show_bug.cgi?id=140562 */
    .mapboxgl-ctrl > button::-moz-focus-inner {
    border: 0;
    padding: 0;
    }
    .mapboxgl-ctrl > button:last-child {
    border-bottom: 0;
    }
    .mapboxgl-ctrl > button:hover {
    background-color: rgba(0,0,0,0.05);
    }
    .mapboxgl-ctrl-icon,
    .mapboxgl-ctrl-icon > div.arrow {
    speak: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    }
    .mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-out {
    padding: 5px;
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27m%207%2C9%20c%20-0.554%2C0%20-1%2C0.446%20-1%2C1%200%2C0.554%200.446%2C1%201%2C1%20l%206%2C0%20c%200.554%2C0%201%2C-0.446%201%2C-1%200%2C-0.554%20-0.446%2C-1%20-1%2C-1%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A");
    }
    .mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-in {
    padding: 5px;
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27M%2010%206%20C%209.446%206%209%206.4459904%209%207%20L%209%209%20L%207%209%20C%206.446%209%206%209.446%206%2010%20C%206%2010.554%206.446%2011%207%2011%20L%209%2011%20L%209%2013%20C%209%2013.55401%209.446%2014%2010%2014%20C%2010.554%2014%2011%2013.55401%2011%2013%20L%2011%2011%20L%2013%2011%20C%2013.554%2011%2014%2010.554%2014%2010%20C%2014%209.446%2013.554%209%2013%209%20L%2011%209%20L%2011%207%20C%2011%206.4459904%2010.554%206%2010%206%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A");
    }
    .mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate {
    padding: 5px;
    background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCIgdmVyc2lvbj0iMS4xIj48cGF0aCBkPSJNMTAgNEM5IDQgOSA1IDkgNUw5IDUuMUE1IDUgMCAwIDAgNS4xIDlMNSA5QzUgOSA0IDkgNCAxMCA0IDExIDUgMTEgNSAxMUw1LjEgMTFBNSA1IDAgMCAwIDkgMTQuOUw5IDE1QzkgMTUgOSAxNiAxMCAxNiAxMSAxNiAxMSAxNSAxMSAxNUwxMSAxNC45QTUgNSAwIDAgMCAxNC45IDExTDE1IDExQzE1IDExIDE2IDExIDE2IDEwIDE2IDkgMTUgOSAxNSA5TDE0LjkgOUE1IDUgMCAwIDAgMTEgNS4xTDExIDVDMTEgNSAxMSA0IDEwIDR6TTEwIDYuNUEzLjUgMy41IDAgMCAxIDEzLjUgMTAgMy41IDMuNSAwIDAgMSAxMCAxMy41IDMuNSAzLjUgMCAwIDEgNi41IDEwIDMuNSAzLjUgMCAwIDEgMTAgNi41ek0xMCA4LjNBMS44IDEuOCAwIDAgMCA4LjMgMTAgMS44IDEuOCAwIDAgMCAxMCAxMS44IDEuOCAxLjggMCAwIDAgMTEuOCAxMCAxLjggMS44IDAgMCAwIDEwIDguM3oiIGZpbGw9IiMzMzMiLz48L3N2Zz4=");
    }

    .mapboxgl-ctrl-icon.mapboxgl-ctrl-compass > div.arrow {
    width: 20px;
    height: 20px;
    margin: 5px;
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%0A%09%3Cpolygon%20fill%3D%27%23333333%27%20points%3D%276%2C9%2010%2C1%2014%2C9%27%2F%3E%0A%09%3Cpolygon%20fill%3D%27%23CCCCCC%27%20points%3D%276%2C11%2010%2C19%2014%2C11%20%27%2F%3E%0A%3C%2Fsvg%3E");
    background-repeat: no-repeat;
    }

    .mapboxgl-ctrl.mapboxgl-ctrl-attrib {
    padding: 0 5px;
    background-color: rgba(255,255,255,0.5);
    margin: 0;
    }
    .mapboxgl-ctrl-attrib a {
    color: rgba(0,0,0,0.75);
    text-decoration: none;
    }
    .mapboxgl-ctrl-attrib a:hover {
    color: inherit;
    text-decoration: underline;
    }
    .mapboxgl-ctrl-attrib .mapbox-improve-map {
    font-weight: bold;
    margin-left: 2px;
    }

    .mapboxgl-ctrl-scale {
    background-color: rgba(255,255,255,0.75);
    font-size: 10px;
    border-width: medium 2px 2px;
    border-style: none solid solid;
    border-color: #333;
    padding: 0 5px;
    color: #333;
    }

    .mapboxgl-popup {
    position: absolute;
    top: 0;
    left: 0;
    display: -webkit-flex;
    display: flex;
    will-change: transform;
    pointer-events: none;
    }
    .mapboxgl-popup-anchor-top,
    .mapboxgl-popup-anchor-top-left,
    .mapboxgl-popup-anchor-top-right {
    -webkit-flex-direction: column;
    flex-direction: column;
    }
    .mapboxgl-popup-anchor-bottom,
    .mapboxgl-popup-anchor-bottom-left,
    .mapboxgl-popup-anchor-bottom-right {
    -webkit-flex-direction: column-reverse;
    flex-direction: column-reverse;
    }
    .mapboxgl-popup-anchor-left {
    -webkit-flex-direction: row;
    flex-direction: row;
    }
    .mapboxgl-popup-anchor-right {
    -webkit-flex-direction: row-reverse;
    flex-direction: row-reverse;
    }
    .mapboxgl-popup-tip {
    width: 0;
    height: 0;
    border: 10px solid transparent;
    z-index: 1;
    }
    .mapboxgl-popup-anchor-top .mapboxgl-popup-tip {
    -webkit-align-self: center;
    align-self: center;
    border-top: none;
    border-bottom-color: #fff;
    }
    .mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip {
    -webkit-align-self: flex-start;
    align-self: flex-start;
    border-top: none;
    border-left: none;
    border-bottom-color: #fff;
    }
    .mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip {
    -webkit-align-self: flex-end;
    align-self: flex-end;
    border-top: none;
    border-right: none;
    border-bottom-color: #fff;
    }
    .mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip {
    -webkit-align-self: center;
    align-self: center;
    border-bottom: none;
    border-top-color: #fff;
    }
    .mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip {
    -webkit-align-self: flex-start;
    align-self: flex-start;
    border-bottom: none;
    border-left: none;
    border-top-color: #fff;
    }
    .mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip {
    -webkit-align-self: flex-end;
    align-self: flex-end;
    border-bottom: none;
    border-right: none;
    border-top-color: #fff;
    }
    .mapboxgl-popup-anchor-left .mapboxgl-popup-tip {
    -webkit-align-self: center;
    align-self: center;
    border-left: none;
    border-right-color: #fff;
    }
    .mapboxgl-popup-anchor-right .mapboxgl-popup-tip {
    -webkit-align-self: center;
    align-self: center;
    border-right: none;
    border-left-color: #fff;
    }
    .mapboxgl-popup-close-button {
    position: absolute;
    right: 0;
    top: 0;
    border: none;
    border-radius: 0 3px 0 0;
    cursor: pointer;
    background-color: rgba(0,0,0,0);
    }
    .mapboxgl-popup-close-button:hover {
    background-color: rgba(0,0,0,0.05);
    }
    .mapboxgl-popup-content {
    position: relative;
    background: #fff;
    border-radius: 3px;
    box-shadow: 0 1px 2px rgba(0,0,0,0.10);
    padding: 10px 10px 15px;
    pointer-events: auto;
    }
    .mapboxgl-popup-anchor-top-left .mapboxgl-popup-content {
    border-top-left-radius: 0;
    }
    .mapboxgl-popup-anchor-top-right .mapboxgl-popup-content {
    border-top-right-radius: 0;
    }
    .mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-content {
    border-bottom-left-radius: 0;
    }
    .mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-content {
    border-bottom-right-radius: 0;
    }

    .mapboxgl-marker {
    position: absolute;
    top: 0;
    left: 0;
    will-change: transform;
    }

    .mapboxgl-crosshair,
    .mapboxgl-crosshair .mapboxgl-interactive,
    .mapboxgl-crosshair .mapboxgl-interactive:active {
    cursor: crosshair;
    }
    .mapboxgl-boxzoom {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    background: #fff;
    border: 2px dotted #202020;
    opacity: 0.5;
    }
    @media print {
    .mapbox-improve-map {
    display:none;
    }
    }
    File renamed without changes.
    File renamed without changes.
    1,566 changes: 0 additions & 1,566 deletions js/geojsonhint.js
    Original file line number Diff line number Diff line change
    @@ -1,1566 +0,0 @@
    (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.geojsonhint = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
    (function (process){
    /* parser generated by jison 0.4.17 */
    /*
    Returns a Parser object of the following structure:
    Parser: {
    yy: {}
    }
    Parser.prototype: {
    yy: {},
    trace: function(),
    symbols_: {associative list: name ==> number},
    terminals_: {associative list: number ==> name},
    productions_: [...],
    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
    table: [...],
    defaultActions: {...},
    parseError: function(str, hash),
    parse: function(input),
    lexer: {
    EOF: 1,
    parseError: function(str, hash),
    setInput: function(input),
    input: function(),
    unput: function(str),
    more: function(),
    less: function(n),
    pastInput: function(),
    upcomingInput: function(),
    showPosition: function(),
    test_match: function(regex_match_array, rule_index),
    next: function(),
    lex: function(),
    begin: function(condition),
    popState: function(),
    _currentRules: function(),
    topState: function(),
    pushState: function(condition),
    options: {
    ranges: boolean (optional: true ==> token location info will include a .range[] member)
    flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
    backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
    },
    performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
    rules: [...],
    conditions: {associative list: name ==> set},
    }
    }
    token location info (@$, _$, etc.): {
    first_line: n,
    last_line: n,
    first_column: n,
    last_column: n,
    range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
    }
    the parseError function receives a 'hash' object with these members for lexer and parser errors: {
    text: (matched text)
    token: (the produced terminal token, if any)
    line: (yylineno)
    }
    while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
    loc: (yylloc)
    expected: (string describing the set of expected tokens)
    recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
    }
    */
    var jsonlint = (function(){
    var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,12],$V1=[1,13],$V2=[1,9],$V3=[1,10],$V4=[1,11],$V5=[1,14],$V6=[1,15],$V7=[14,18,22,24],$V8=[18,22],$V9=[22,24];
    var parser = {trace: function trace() { },
    yy: {},
    symbols_: {"error":2,"JSONString":3,"STRING":4,"JSONNumber":5,"NUMBER":6,"JSONNullLiteral":7,"NULL":8,"JSONBooleanLiteral":9,"TRUE":10,"FALSE":11,"JSONText":12,"JSONValue":13,"EOF":14,"JSONObject":15,"JSONArray":16,"{":17,"}":18,"JSONMemberList":19,"JSONMember":20,":":21,",":22,"[":23,"]":24,"JSONElementList":25,"$accept":0,"$end":1},
    terminals_: {2:"error",4:"STRING",6:"NUMBER",8:"NULL",10:"TRUE",11:"FALSE",14:"EOF",17:"{",18:"}",21:":",22:",",23:"[",24:"]"},
    productions_: [0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]],
    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
    /* this == yyval */

    var $0 = $$.length - 1;
    switch (yystate) {
    case 1:
    // replace escaped characters with actual character
    this.$ = yytext.replace(/\\(\\|")/g, "$"+"1")
    .replace(/\\n/g,'\n')
    .replace(/\\r/g,'\r')
    .replace(/\\t/g,'\t')
    .replace(/\\v/g,'\v')
    .replace(/\\f/g,'\f')
    .replace(/\\b/g,'\b');

    break;
    case 2:
    this.$ = Number(yytext);
    break;
    case 3:
    this.$ = null;
    break;
    case 4:
    this.$ = true;
    break;
    case 5:
    this.$ = false;
    break;
    case 6:
    return this.$ = $$[$0-1];
    break;
    case 13:
    this.$ = {}; Object.defineProperty(this.$, '__line__', {
    value: this._$.first_line,
    enumerable: false
    })
    break;
    case 14: case 19:
    this.$ = $$[$0-1]; Object.defineProperty(this.$, '__line__', {
    value: this._$.first_line,
    enumerable: false
    })
    break;
    case 15:
    this.$ = [$$[$0-2], $$[$0]];
    break;
    case 16:
    this.$ = {}; this.$[$$[$0][0]] = $$[$0][1];
    break;
    case 17:

    this.$ = $$[$0-2];
    if ($$[$0-2][$$[$0][0]] !== undefined) {
    if (!this.$.__duplicateProperties__) {
    Object.defineProperty(this.$, '__duplicateProperties__', {
    value: [],
    enumerable: false
    });
    }
    this.$.__duplicateProperties__.push($$[$0][0]);
    }
    $$[$0-2][$$[$0][0]] = $$[$0][1];

    break;
    case 18:
    this.$ = []; Object.defineProperty(this.$, '__line__', {
    value: this._$.first_line,
    enumerable: false
    })
    break;
    case 20:
    this.$ = [$$[$0]];
    break;
    case 21:
    this.$ = $$[$0-2]; $$[$0-2].push($$[$0]);
    break;
    }
    },
    table: [{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,12:1,13:2,15:7,16:8,17:$V5,23:$V6},{1:[3]},{14:[1,16]},o($V7,[2,7]),o($V7,[2,8]),o($V7,[2,9]),o($V7,[2,10]),o($V7,[2,11]),o($V7,[2,12]),o($V7,[2,3]),o($V7,[2,4]),o($V7,[2,5]),o([14,18,21,22,24],[2,1]),o($V7,[2,2]),{3:20,4:$V0,18:[1,17],19:18,20:19},{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:23,15:7,16:8,17:$V5,23:$V6,24:[1,21],25:22},{1:[2,6]},o($V7,[2,13]),{18:[1,24],22:[1,25]},o($V8,[2,16]),{21:[1,26]},o($V7,[2,18]),{22:[1,28],24:[1,27]},o($V9,[2,20]),o($V7,[2,14]),{3:20,4:$V0,20:29},{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:30,15:7,16:8,17:$V5,23:$V6},o($V7,[2,19]),{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:31,15:7,16:8,17:$V5,23:$V6},o($V8,[2,17]),o($V8,[2,15]),o($V9,[2,21])],
    defaultActions: {16:[2,6]},
    parseError: function parseError(str, hash) {
    if (hash.recoverable) {
    this.trace(str);
    } else {
    function _parseError (msg, hash) {
    this.message = msg;
    this.hash = hash;
    }
    _parseError.prototype = Error;

    throw new _parseError(str, hash);
    }
    },
    parse: function parse(input) {
    var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
    var args = lstack.slice.call(arguments, 1);
    var lexer = Object.create(this.lexer);
    var sharedState = { yy: {} };
    for (var k in this.yy) {
    if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
    sharedState.yy[k] = this.yy[k];
    }
    }
    lexer.setInput(input, sharedState.yy);
    sharedState.yy.lexer = lexer;
    sharedState.yy.parser = this;
    if (typeof lexer.yylloc == 'undefined') {
    lexer.yylloc = {};
    }
    var yyloc = lexer.yylloc;
    lstack.push(yyloc);
    var ranges = lexer.options && lexer.options.ranges;
    if (typeof sharedState.yy.parseError === 'function') {
    this.parseError = sharedState.yy.parseError;
    } else {
    this.parseError = Object.getPrototypeOf(this).parseError;
    }
    function popStack(n) {
    stack.length = stack.length - 2 * n;
    vstack.length = vstack.length - n;
    lstack.length = lstack.length - n;
    }
    _token_stack:
    var lex = function () {
    var token;
    token = lexer.lex() || EOF;
    if (typeof token !== 'number') {
    token = self.symbols_[token] || token;
    }
    return token;
    };
    var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
    while (true) {
    state = stack[stack.length - 1];
    if (this.defaultActions[state]) {
    action = this.defaultActions[state];
    } else {
    if (symbol === null || typeof symbol == 'undefined') {
    symbol = lex();
    }
    action = table[state] && table[state][symbol];
    }
    if (typeof action === 'undefined' || !action.length || !action[0]) {
    var errStr = '';
    expected = [];
    for (p in table[state]) {
    if (this.terminals_[p] && p > TERROR) {
    expected.push('\'' + this.terminals_[p] + '\'');
    }
    }
    if (lexer.showPosition) {
    errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
    } else {
    errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
    }
    this.parseError(errStr, {
    text: lexer.match,
    token: this.terminals_[symbol] || symbol,
    line: lexer.yylineno,
    loc: yyloc,
    expected: expected
    });
    }
    if (action[0] instanceof Array && action.length > 1) {
    throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
    }
    switch (action[0]) {
    case 1:
    stack.push(symbol);
    vstack.push(lexer.yytext);
    lstack.push(lexer.yylloc);
    stack.push(action[1]);
    symbol = null;
    if (!preErrorSymbol) {
    yyleng = lexer.yyleng;
    yytext = lexer.yytext;
    yylineno = lexer.yylineno;
    yyloc = lexer.yylloc;
    if (recovering > 0) {
    recovering--;
    }
    } else {
    symbol = preErrorSymbol;
    preErrorSymbol = null;
    }
    break;
    case 2:
    len = this.productions_[action[1]][1];
    yyval.$ = vstack[vstack.length - len];
    yyval._$ = {
    first_line: lstack[lstack.length - (len || 1)].first_line,
    last_line: lstack[lstack.length - 1].last_line,
    first_column: lstack[lstack.length - (len || 1)].first_column,
    last_column: lstack[lstack.length - 1].last_column
    };
    if (ranges) {
    yyval._$.range = [
    lstack[lstack.length - (len || 1)].range[0],
    lstack[lstack.length - 1].range[1]
    ];
    }
    r = this.performAction.apply(yyval, [
    yytext,
    yyleng,
    yylineno,
    sharedState.yy,
    action[1],
    vstack,
    lstack
    ].concat(args));
    if (typeof r !== 'undefined') {
    return r;
    }
    if (len) {
    stack = stack.slice(0, -1 * len * 2);
    vstack = vstack.slice(0, -1 * len);
    lstack = lstack.slice(0, -1 * len);
    }
    stack.push(this.productions_[action[1]][0]);
    vstack.push(yyval.$);
    lstack.push(yyval._$);
    newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
    stack.push(newState);
    break;
    case 3:
    return true;
    }
    }
    return true;
    }};
    /* generated by jison-lex 0.3.4 */
    var lexer = (function(){
    var lexer = ({

    EOF:1,

    parseError:function parseError(str, hash) {
    if (this.yy.parser) {
    this.yy.parser.parseError(str, hash);
    } else {
    throw new Error(str);
    }
    },

    // resets the lexer, sets new input
    setInput:function (input, yy) {
    this.yy = yy || this.yy || {};
    this._input = input;
    this._more = this._backtrack = this.done = false;
    this.yylineno = this.yyleng = 0;
    this.yytext = this.matched = this.match = '';
    this.conditionStack = ['INITIAL'];
    this.yylloc = {
    first_line: 1,
    first_column: 0,
    last_line: 1,
    last_column: 0
    };
    if (this.options.ranges) {
    this.yylloc.range = [0,0];
    }
    this.offset = 0;
    return this;
    },

    // consumes and returns one char from the input
    input:function () {
    var ch = this._input[0];
    this.yytext += ch;
    this.yyleng++;
    this.offset++;
    this.match += ch;
    this.matched += ch;
    var lines = ch.match(/(?:\r\n?|\n).*/g);
    if (lines) {
    this.yylineno++;
    this.yylloc.last_line++;
    } else {
    this.yylloc.last_column++;
    }
    if (this.options.ranges) {
    this.yylloc.range[1]++;
    }

    this._input = this._input.slice(1);
    return ch;
    },

    // unshifts one char (or a string) into the input
    unput:function (ch) {
    var len = ch.length;
    var lines = ch.split(/(?:\r\n?|\n)/g);

    this._input = ch + this._input;
    this.yytext = this.yytext.substr(0, this.yytext.length - len);
    //this.yyleng -= len;
    this.offset -= len;
    var oldLines = this.match.split(/(?:\r\n?|\n)/g);
    this.match = this.match.substr(0, this.match.length - 1);
    this.matched = this.matched.substr(0, this.matched.length - 1);

    if (lines.length - 1) {
    this.yylineno -= lines.length - 1;
    }
    var r = this.yylloc.range;

    this.yylloc = {
    first_line: this.yylloc.first_line,
    last_line: this.yylineno + 1,
    first_column: this.yylloc.first_column,
    last_column: lines ?
    (lines.length === oldLines.length ? this.yylloc.first_column : 0)
    + oldLines[oldLines.length - lines.length].length - lines[0].length :
    this.yylloc.first_column - len
    };

    if (this.options.ranges) {
    this.yylloc.range = [r[0], r[0] + this.yyleng - len];
    }
    this.yyleng = this.yytext.length;
    return this;
    },

    // When called from action, caches matched text and appends it on next action
    more:function () {
    this._more = true;
    return this;
    },

    // When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
    reject:function () {
    if (this.options.backtrack_lexer) {
    this._backtrack = true;
    } else {
    return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
    text: "",
    token: null,
    line: this.yylineno
    });

    }
    return this;
    },

    // retain first n characters of the match
    less:function (n) {
    this.unput(this.match.slice(n));
    },

    // displays already matched input, i.e. for error messages
    pastInput:function () {
    var past = this.matched.substr(0, this.matched.length - this.match.length);
    return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
    },

    // displays upcoming input, i.e. for error messages
    upcomingInput:function () {
    var next = this.match;
    if (next.length < 20) {
    next += this._input.substr(0, 20-next.length);
    }
    return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
    },

    // displays the character position where the lexing error occurred, i.e. for error messages
    showPosition:function () {
    var pre = this.pastInput();
    var c = new Array(pre.length + 1).join("-");
    return pre + this.upcomingInput() + "\n" + c + "^";
    },

    // test the lexed token: return FALSE when not a match, otherwise return token
    test_match:function (match, indexed_rule) {
    var token,
    lines,
    backup;

    if (this.options.backtrack_lexer) {
    // save context
    backup = {
    yylineno: this.yylineno,
    yylloc: {
    first_line: this.yylloc.first_line,
    last_line: this.last_line,
    first_column: this.yylloc.first_column,
    last_column: this.yylloc.last_column
    },
    yytext: this.yytext,
    match: this.match,
    matches: this.matches,
    matched: this.matched,
    yyleng: this.yyleng,
    offset: this.offset,
    _more: this._more,
    _input: this._input,
    yy: this.yy,
    conditionStack: this.conditionStack.slice(0),
    done: this.done
    };
    if (this.options.ranges) {
    backup.yylloc.range = this.yylloc.range.slice(0);
    }
    }

    lines = match[0].match(/(?:\r\n?|\n).*/g);
    if (lines) {
    this.yylineno += lines.length;
    }
    this.yylloc = {
    first_line: this.yylloc.last_line,
    last_line: this.yylineno + 1,
    first_column: this.yylloc.last_column,
    last_column: lines ?
    lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
    this.yylloc.last_column + match[0].length
    };
    this.yytext += match[0];
    this.match += match[0];
    this.matches = match;
    this.yyleng = this.yytext.length;
    if (this.options.ranges) {
    this.yylloc.range = [this.offset, this.offset += this.yyleng];
    }
    this._more = false;
    this._backtrack = false;
    this._input = this._input.slice(match[0].length);
    this.matched += match[0];
    token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
    if (this.done && this._input) {
    this.done = false;
    }
    if (token) {
    return token;
    } else if (this._backtrack) {
    // recover context
    for (var k in backup) {
    this[k] = backup[k];
    }
    return false; // rule action called reject() implying the next rule should be tested instead.
    }
    return false;
    },

    // return next match in input
    next:function () {
    if (this.done) {
    return this.EOF;
    }
    if (!this._input) {
    this.done = true;
    }

    var token,
    match,
    tempMatch,
    index;
    if (!this._more) {
    this.yytext = '';
    this.match = '';
    }
    var rules = this._currentRules();
    for (var i = 0; i < rules.length; i++) {
    tempMatch = this._input.match(this.rules[rules[i]]);
    if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
    match = tempMatch;
    index = i;
    if (this.options.backtrack_lexer) {
    token = this.test_match(tempMatch, rules[i]);
    if (token !== false) {
    return token;
    } else if (this._backtrack) {
    match = false;
    continue; // rule action called reject() implying a rule MISmatch.
    } else {
    // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
    return false;
    }
    } else if (!this.options.flex) {
    break;
    }
    }
    }
    if (match) {
    token = this.test_match(match, rules[index]);
    if (token !== false) {
    return token;
    }
    // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
    return false;
    }
    if (this._input === "") {
    return this.EOF;
    } else {
    return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
    text: "",
    token: null,
    line: this.yylineno
    });
    }
    },

    // return next match that has a token
    lex:function lex() {
    var r = this.next();
    if (r) {
    return r;
    } else {
    return this.lex();
    }
    },

    // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
    begin:function begin(condition) {
    this.conditionStack.push(condition);
    },

    // pop the previously active lexer condition state off the condition stack
    popState:function popState() {
    var n = this.conditionStack.length - 1;
    if (n > 0) {
    return this.conditionStack.pop();
    } else {
    return this.conditionStack[0];
    }
    },

    // produce the lexer rule set which is active for the currently active lexer condition state
    _currentRules:function _currentRules() {
    if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
    return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
    } else {
    return this.conditions["INITIAL"].rules;
    }
    },

    // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
    topState:function topState(n) {
    n = this.conditionStack.length - 1 - Math.abs(n || 0);
    if (n >= 0) {
    return this.conditionStack[n];
    } else {
    return "INITIAL";
    }
    },

    // alias for begin(condition)
    pushState:function pushState(condition) {
    this.begin(condition);
    },

    // return the number of states currently on the stack
    stateStackSize:function stateStackSize() {
    return this.conditionStack.length;
    },
    options: {},
    performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
    var YYSTATE=YY_START;
    switch($avoiding_name_collisions) {
    case 0:/* skip whitespace */
    break;
    case 1:return 6
    break;
    case 2:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2); return 4
    break;
    case 3:return 17
    break;
    case 4:return 18
    break;
    case 5:return 23
    break;
    case 6:return 24
    break;
    case 7:return 22
    break;
    case 8:return 21
    break;
    case 9:return 10
    break;
    case 10:return 11
    break;
    case 11:return 8
    break;
    case 12:return 14
    break;
    case 13:return 'INVALID'
    break;
    }
    },
    rules: [/^(?:\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\.[0-9]+)?([eE][-+]?[0-9]+)?\b)/,/^(?:"(?:\\[\\"bfnrt\/]|\\u[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*")/,/^(?:\{)/,/^(?:\})/,/^(?:\[)/,/^(?:\])/,/^(?:,)/,/^(?::)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:null\b)/,/^(?:$)/,/^(?:.)/],
    conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"inclusive":true}}
    });
    return lexer;
    })();
    parser.lexer = lexer;
    function Parser () {
    this.yy = {};
    }
    Parser.prototype = parser;parser.Parser = Parser;
    return new Parser;
    })();


    if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
    exports.parser = jsonlint;
    exports.Parser = jsonlint.Parser;
    exports.parse = function () { return jsonlint.parse.apply(jsonlint, arguments); };
    exports.main = function commonjsMain(args) {
    if (!args[1]) {
    console.log('Usage: '+args[0]+' FILE');
    process.exit(1);
    }
    var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8");
    return exports.parser.parse(source);
    };
    if (typeof module !== 'undefined' && require.main === module) {
    exports.main(process.argv.slice(1));
    }
    }
    }).call(this,require('_process'))
    },{"_process":6,"fs":4,"path":5}],2:[function(require,module,exports){
    /**
    * @alias geojsonhint
    * @param {(string|object)} GeoJSON given as a string or as an object
    * @param {Object} options
    * @param {boolean} [options.noDuplicateMembers=true] forbid repeated
    * properties. This is only available for string input, becaused parsed
    * Objects cannot have duplicate properties.
    * @returns {Array<Object>} an array of errors
    */
    function hint(gj, options) {

    var errors = [];
    var precisionWarningCount = 0;

    function root(_) {

    if ((!options || options.noDuplicateMembers !== false) &&
    _.__duplicateProperties__) {
    errors.push({
    message: 'An object contained duplicate members, making parsing ambigous: ' + _.__duplicateProperties__.join(', '),
    line: _.__line__
    });
    }

    if (!_.type) {
    errors.push({
    message: 'The type property is required and was not found',
    line: _.__line__
    });
    } else if (!types[_.type]) {
    var expectedType = typesLower[_.type.toLowerCase()];
    if (expectedType !== undefined) {
    errors.push({
    message: 'Expected ' + expectedType + ' but got ' + _.type + ' (case sensitive)',
    line: _.__line__
    });
    } else {
    errors.push({
    message: 'The type ' + _.type + ' is unknown',
    line: _.__line__
    });
    }
    } else if (_) {
    types[_.type](_);
    }
    }

    function everyIs(_, type) {
    // make a single exception because typeof null === 'object'
    return _.every(function(x) { return (x !== null) && (typeof x === type); });
    }

    function requiredProperty(_, name, type) {
    if (typeof _[name] === 'undefined') {
    return errors.push({
    message: '"' + name + '" property required',
    line: _.__line__
    });
    } else if (type === 'array') {
    if (!Array.isArray(_[name])) {
    return errors.push({
    message: '"' + name +
    '" property should be an array, but is an ' +
    (typeof _[name]) + ' instead',
    line: _.__line__
    });
    }
    } else if (type && typeof _[name] !== type) {
    return errors.push({
    message: '"' + name +
    '" property should be ' + (type) +
    ', but is an ' + (typeof _[name]) + ' instead',
    line: _.__line__
    });
    }
    }

    // http://geojson.org/geojson-spec.html#feature-collection-objects
    function FeatureCollection(featureCollection) {
    crs(featureCollection);
    bbox(featureCollection);
    if (featureCollection.properties !== undefined) {
    errors.push({
    message: 'FeatureCollection object cannot contain a "properties" member',
    line: featureCollection.__line__
    });
    }
    if (featureCollection.coordinates !== undefined) {
    errors.push({
    message: 'FeatureCollection object cannot contain a "coordinates" member',
    line: featureCollection.__line__
    });
    }
    if (!requiredProperty(featureCollection, 'features', 'array')) {
    if (!everyIs(featureCollection.features, 'object')) {
    return errors.push({
    message: 'Every feature must be an object',
    line: featureCollection.__line__
    });
    }
    featureCollection.features.forEach(Feature);
    }
    }

    // http://geojson.org/geojson-spec.html#positions
    function position(_, line) {
    if (!Array.isArray(_)) {
    return errors.push({
    message: 'position should be an array, is a ' + (typeof _) +
    ' instead',
    line: _.__line__ || line
    });
    } else {
    if (_.length < 2) {
    return errors.push({
    message: 'position must have 2 or more elements',
    line: _.__line__ || line
    });
    }
    if (_.length > 3) {
    return errors.push({
    message: 'position should not have more than 3 elements',
    line: _.__line__ || line
    });
    }
    if (!everyIs(_, 'number')) {
    return errors.push({
    message: 'each element in a position must be a number',
    line: _.__line__ || line
    });
    }

    var maxPrecisionWarnings = 10;
    var maxPrecision = 6;
    var num;
    if (precisionWarningCount == maxPrecisionWarnings) {
    precisionWarningCount += 1;
    return errors.push({
    message: "truncated warnings: we've encountered coordinate precision warning " + maxPrecisionWarnings + " times, no more warnings will be reported",
    level: "warn",
    line: _.__line__ || line
    });
    } else if (precisionWarningCount < maxPrecisionWarnings) {
    _.forEach(function(num) {
    // TODO there has got to be a better way. Check original text?
    // By this point number has already been parsed to a float...
    var precision = 0;
    var decimalStr = (num + "").split(".")[1];
    if (decimalStr !== undefined)
    precision = decimalStr.length;
    if (precision > maxPrecision) {
    precisionWarningCount += 1;
    return errors.push({
    message: "precision of coordinates should be reduced",
    level: "warn",
    line: _.__line__ || line
    });
    }

    });
    }
    }
    }

    function positionArray(coords, type, depth, line) {
    if (line === undefined && coords.__line__ !== undefined) {
    line = coords.__line__;
    }
    if (depth === 0) {
    return position(coords, line);
    } else {
    if (depth === 1 && type) {
    if (type === 'LinearRing') {
    if (!Array.isArray(coords[coords.length - 1])) {
    errors.push({
    message: 'a number was found where a coordinate array should have been found: this needs to be nested more deeply',
    line: line
    });
    return true;
    }
    if (coords.length < 4) {
    errors.push({
    message: 'a LinearRing of coordinates needs to have four or more positions',
    line: line
    });
    }
    if (coords.length &&
    (coords[coords.length - 1].length !== coords[0].length ||
    !coords[coords.length - 1].every(function(position, index) {
    return coords[0][index] === position;
    }))) {
    errors.push({
    message: 'the first and last positions in a LinearRing of coordinates must be the same',
    line: line
    });
    }
    } else if (type === 'Line' && coords.length < 2) {
    return errors.push({
    message: 'a line needs to have two or more coordinates to be valid',
    line: line
    });
    }
    }
    if (!Array.isArray(coords)) {
    errors.push({
    message: 'a number was found where a coordinate array should have been found: this needs to be nested more deeply',
    line: line
    });
    } else {
    coords.forEach(function(c) {
    positionArray(c, type, depth - 1, c.__line__ || line);
    });
    }
    }
    }

    function rightHandRule (geometry) {
    var rhr = true;
    if (geometry.type == 'Polygon') {
    rhr = isPolyRHR(geometry.coordinates);
    } else if (geometry.type == 'MultiPolygon') {
    if (!geometry.coordinates.every(isPolyRHR))
    rhr = false;
    }
    if (!rhr) {
    errors.push({
    message: 'Polygons and MultiPolygons should follow the right-hand rule',
    level: 'warn',
    line: geometry.__line__
    });
    }
    }

    function isPolyRHR (coords) {
    if (coords && coords.length > 0) {
    if (!isRingClockwise(coords[0]))
    return false;
    var interiorCoords = coords.slice(1, coords.length);
    if (interiorCoords.some(isRingClockwise))
    return false;
    }
    return true;
    }

    function isRingClockwise (coords) {
    var area = 0;
    if (coords.length > 2) {
    var p1, p2;
    for (var i = 0; i < coords.length - 1; i++) {
    p1 = coords[i];
    p2 = coords[i + 1];
    area += rad(p2[0] - p1[0]) * (2 + Math.sin(rad(p1[1])) + Math.sin(rad(p2[1])));
    }
    }

    return area >= 0;
    }

    function rad(x) {
    return x * Math.PI / 180;
    }

    function crs(_) {
    if (!_.crs) return;
    var defaultCRSName = "urn:ogc:def:crs:OGC:1.3:CRS84";
    if (typeof _.crs === 'object' && _.crs.properties && _.crs.properties.name === defaultCRSName) {
    errors.push({
    message: "old-style crs member is not recommended, this object is equivalent to the default and should be removed",
    level: "warn",
    line: _.__line__
    });
    } else {
    errors.push({
    message: "old-style crs member is not recommended",
    level: "warn",
    line: _.__line__
    });
    }
    }

    function bbox(_) {
    if (!_.bbox) { return; }
    if (Array.isArray(_.bbox)) {
    if (!everyIs(_.bbox, 'number')) {
    errors.push({
    message: 'each element in a bbox property must be a number',
    line: _.bbox.__line__
    });
    }
    if (!(_.bbox.length == 4 || _.bbox.length == 6)) {
    errors.push({
    message: 'bbox must contain 4 elements (for 2D) or 6 elements (for 3D)',
    line: _.bbox.__line__
    });
    }
    return errors.length;
    } else {
    errors.push({
    message: 'bbox property must be an array of numbers, but is a ' + (typeof _.bbox),
    line: _.__line__
    });
    }
    }

    function geometrySemantics(geom) {
    if (geom.properties !== undefined) {
    errors.push({
    message: 'geometry object cannot contain a "properties" member',
    line: geom.__line__
    });
    }
    if (geom.geometry !== undefined) {
    errors.push({
    message: 'geometry object cannot contain a "geometry" member',
    line: geom.__line__
    });
    }
    if (geom.features !== undefined) {
    errors.push({
    message: 'geometry object cannot contain a "features" member',
    line: geom.__line__
    });
    }
    }

    // http://geojson.org/geojson-spec.html#point
    function Point(point) {
    crs(point);
    bbox(point);
    geometrySemantics(point);
    if (!requiredProperty(point, 'coordinates', 'array')) {
    position(point.coordinates);
    }
    }

    // http://geojson.org/geojson-spec.html#polygon
    function Polygon(polygon) {
    crs(polygon);
    bbox(polygon);
    if (!requiredProperty(polygon, 'coordinates', 'array')) {
    if (!positionArray(polygon.coordinates, 'LinearRing', 2)) {
    rightHandRule(polygon);
    }
    }
    }

    // http://geojson.org/geojson-spec.html#multipolygon
    function MultiPolygon(multiPolygon) {
    crs(multiPolygon);
    bbox(multiPolygon);
    if (!requiredProperty(multiPolygon, 'coordinates', 'array')) {
    if (!positionArray(multiPolygon.coordinates, 'LinearRing', 3)) {
    rightHandRule(multiPolygon);
    }
    }
    }

    // http://geojson.org/geojson-spec.html#linestring
    function LineString(lineString) {
    crs(lineString);
    bbox(lineString);
    if (!requiredProperty(lineString, 'coordinates', 'array')) {
    positionArray(lineString.coordinates, 'Line', 1);
    }
    }

    // http://geojson.org/geojson-spec.html#multilinestring
    function MultiLineString(multiLineString) {
    crs(multiLineString);
    bbox(multiLineString);
    if (!requiredProperty(multiLineString, 'coordinates', 'array')) {
    positionArray(multiLineString.coordinates, 'Line', 2);
    }
    }

    // http://geojson.org/geojson-spec.html#multipoint
    function MultiPoint(multiPoint) {
    crs(multiPoint);
    bbox(multiPoint);
    if (!requiredProperty(multiPoint, 'coordinates', 'array')) {
    positionArray(multiPoint.coordinates, '', 1);
    }
    }

    function GeometryCollection(geometryCollection) {
    crs(geometryCollection);
    bbox(geometryCollection);
    if (!requiredProperty(geometryCollection, 'geometries', 'array')) {
    if (!everyIs(geometryCollection.geometries, 'object')) {
    errors.push({
    message: 'The geometries array in a GeometryCollection must contain only geometry objects',
    line: geometryCollection.__line__
    });
    }
    if (geometryCollection.geometries.length == 1) {
    errors.push({
    message: 'GeometryCollection with a single geometry should be avoided in favor of single part or a single object of multi-part type',
    level: 'warn',
    line: geometryCollection.geometries.__line__
    });
    }
    geometryCollection.geometries.forEach(function(geometry) {
    if (geometry) {
    if (geometry.type === "GeometryCollection") {
    errors.push({
    message: "GeometryCollection should avoid nested geometry collections",
    level: 'warn',
    line: geometryCollection.geometries.__line__
    });
    }
    root(geometry);
    }
    });
    }
    }

    function Feature(feature) {
    crs(feature);
    bbox(feature);
    // https://github.com/geojson/draft-geojson/blob/master/middle.mkd#feature-object
    if (feature.id !== undefined &&
    typeof feature.id !== 'string' &&
    typeof feature.id !== 'number') {
    errors.push({
    message: 'Feature "id" property must have a string or number value',
    line: feature.__line__
    });
    }
    if (feature.features !== undefined) {
    errors.push({
    message: 'Feature object cannot contain a "features" member',
    line: feature.__line__
    });
    }
    if (feature.coordinates !== undefined) {
    errors.push({
    message: 'Feature object cannot contain a "coordinates" member',
    line: feature.__line__
    });
    }
    if (feature.type !== 'Feature') {
    errors.push({
    message: 'GeoJSON features must have a type=feature property',
    line: feature.__line__
    });
    }
    requiredProperty(feature, 'properties', 'object');
    if (!requiredProperty(feature, 'geometry', 'object')) {
    // http://geojson.org/geojson-spec.html#feature-objects
    // tolerate null geometry
    if (feature.geometry) root(feature.geometry);
    }
    }

    var types = {
    Point: Point,
    Feature: Feature,
    MultiPoint: MultiPoint,
    LineString: LineString,
    MultiLineString: MultiLineString,
    FeatureCollection: FeatureCollection,
    GeometryCollection: GeometryCollection,
    Polygon: Polygon,
    MultiPolygon: MultiPolygon
    };

    var typesLower = Object.keys(types).reduce(function(prev, curr) {
    prev[curr.toLowerCase()] = curr;
    return prev;
    }, {});

    if (typeof gj !== 'object' ||
    gj === null ||
    gj === undefined) {
    errors.push({
    message: 'The root of a GeoJSON object must be an object.',
    line: 0
    });
    return errors;
    }

    root(gj);

    errors.forEach(function(err) {
    if (err.hasOwnProperty('line') && err.line === undefined) {
    delete err.line;
    }
    });

    return errors;
    }

    module.exports.hint = hint;

    },{}],3:[function(require,module,exports){
    var jsonlint = require('jsonlint-lines'),
    geojsonHintObject = require('./object');

    /**
    * @alias geojsonhint
    * @param {(string|object)} GeoJSON given as a string or as an object
    * @param {Object} options
    * @param {boolean} [options.noDuplicateMembers=true] forbid repeated
    * properties. This is only available for string input, becaused parsed
    * Objects cannot have duplicate properties.
    * @returns {Array<Object>} an array of errors
    */
    function hint(str, options) {

    var gj, errors = [];

    if (typeof str === 'object') {
    gj = str;
    } else if (typeof str === 'string') {
    try {
    gj = jsonlint.parse(str);
    } catch(e) {
    var match = e.message.match(/line (\d+)/);
    var lineNumber = parseInt(match[1], 10);
    return [{
    line: lineNumber - 1,
    message: e.message,
    error: e
    }];
    }
    } else {
    return [{
    message: 'Expected string or object as input',
    line: 0
    }];
    }

    errors = errors.concat(geojsonHintObject.hint(gj, options));

    return errors;
    }

    module.exports.hint = hint;

    },{"./object":2,"jsonlint-lines":1}],4:[function(require,module,exports){

    },{}],5:[function(require,module,exports){
    (function (process){
    // Copyright Joyent, Inc. and other Node contributors.
    //
    // Permission is hereby granted, free of charge, to any person obtaining a
    // copy of this software and associated documentation files (the
    // "Software"), to deal in the Software without restriction, including
    // without limitation the rights to use, copy, modify, merge, publish,
    // distribute, sublicense, and/or sell copies of the Software, and to permit
    // persons to whom the Software is furnished to do so, subject to the
    // following conditions:
    //
    // The above copyright notice and this permission notice shall be included
    // in all copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
    // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
    // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
    // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
    // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
    // USE OR OTHER DEALINGS IN THE SOFTWARE.

    // resolves . and .. elements in a path array with directory names there
    // must be no slashes, empty elements, or device names (c:\) in the array
    // (so also no leading and trailing slashes - it does not distinguish
    // relative and absolute paths)
    function normalizeArray(parts, allowAboveRoot) {
    // if the path tries to go above the root, `up` ends up > 0
    var up = 0;
    for (var i = parts.length - 1; i >= 0; i--) {
    var last = parts[i];
    if (last === '.') {
    parts.splice(i, 1);
    } else if (last === '..') {
    parts.splice(i, 1);
    up++;
    } else if (up) {
    parts.splice(i, 1);
    up--;
    }
    }

    // if the path is allowed to go above the root, restore leading ..s
    if (allowAboveRoot) {
    for (; up--; up) {
    parts.unshift('..');
    }
    }

    return parts;
    }

    // Split a filename into [root, dir, basename, ext], unix version
    // 'root' is just a slash, or nothing.
    var splitPathRe =
    /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
    var splitPath = function(filename) {
    return splitPathRe.exec(filename).slice(1);
    };

    // path.resolve([from ...], to)
    // posix version
    exports.resolve = function() {
    var resolvedPath = '',
    resolvedAbsolute = false;

    for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    var path = (i >= 0) ? arguments[i] : process.cwd();

    // Skip empty and invalid entries
    if (typeof path !== 'string') {
    throw new TypeError('Arguments to path.resolve must be strings');
    } else if (!path) {
    continue;
    }

    resolvedPath = path + '/' + resolvedPath;
    resolvedAbsolute = path.charAt(0) === '/';
    }

    // At this point the path should be resolved to a full absolute path, but
    // handle relative paths to be safe (might happen when process.cwd() fails)

    // Normalize the path
    resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
    return !!p;
    }), !resolvedAbsolute).join('/');

    return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
    };

    // path.normalize(path)
    // posix version
    exports.normalize = function(path) {
    var isAbsolute = exports.isAbsolute(path),
    trailingSlash = substr(path, -1) === '/';

    // Normalize the path
    path = normalizeArray(filter(path.split('/'), function(p) {
    return !!p;
    }), !isAbsolute).join('/');

    if (!path && !isAbsolute) {
    path = '.';
    }
    if (path && trailingSlash) {
    path += '/';
    }

    return (isAbsolute ? '/' : '') + path;
    };

    // posix version
    exports.isAbsolute = function(path) {
    return path.charAt(0) === '/';
    };

    // posix version
    exports.join = function() {
    var paths = Array.prototype.slice.call(arguments, 0);
    return exports.normalize(filter(paths, function(p, index) {
    if (typeof p !== 'string') {
    throw new TypeError('Arguments to path.join must be strings');
    }
    return p;
    }).join('/'));
    };


    // path.relative(from, to)
    // posix version
    exports.relative = function(from, to) {
    from = exports.resolve(from).substr(1);
    to = exports.resolve(to).substr(1);

    function trim(arr) {
    var start = 0;
    for (; start < arr.length; start++) {
    if (arr[start] !== '') break;
    }

    var end = arr.length - 1;
    for (; end >= 0; end--) {
    if (arr[end] !== '') break;
    }

    if (start > end) return [];
    return arr.slice(start, end - start + 1);
    }

    var fromParts = trim(from.split('/'));
    var toParts = trim(to.split('/'));

    var length = Math.min(fromParts.length, toParts.length);
    var samePartsLength = length;
    for (var i = 0; i < length; i++) {
    if (fromParts[i] !== toParts[i]) {
    samePartsLength = i;
    break;
    }
    }

    var outputParts = [];
    for (var i = samePartsLength; i < fromParts.length; i++) {
    outputParts.push('..');
    }

    outputParts = outputParts.concat(toParts.slice(samePartsLength));

    return outputParts.join('/');
    };

    exports.sep = '/';
    exports.delimiter = ':';

    exports.dirname = function(path) {
    var result = splitPath(path),
    root = result[0],
    dir = result[1];

    if (!root && !dir) {
    // No dirname whatsoever
    return '.';
    }

    if (dir) {
    // It has a dirname, strip trailing slash
    dir = dir.substr(0, dir.length - 1);
    }

    return root + dir;
    };


    exports.basename = function(path, ext) {
    var f = splitPath(path)[2];
    // TODO: make this comparison case-insensitive on windows?
    if (ext && f.substr(-1 * ext.length) === ext) {
    f = f.substr(0, f.length - ext.length);
    }
    return f;
    };


    exports.extname = function(path) {
    return splitPath(path)[3];
    };

    function filter (xs, f) {
    if (xs.filter) return xs.filter(f);
    var res = [];
    for (var i = 0; i < xs.length; i++) {
    if (f(xs[i], i, xs)) res.push(xs[i]);
    }
    return res;
    }

    // String.prototype.substr - negative index don't work in IE8
    var substr = 'ab'.substr(-1) === 'b'
    ? function (str, start, len) { return str.substr(start, len) }
    : function (str, start, len) {
    if (start < 0) start = str.length + start;
    return str.substr(start, len);
    }
    ;

    }).call(this,require('_process'))
    },{"_process":6}],6:[function(require,module,exports){
    // shim for using process in browser

    var process = module.exports = {};
    var queue = [];
    var draining = false;
    var currentQueue;
    var queueIndex = -1;

    function cleanUpNextTick() {
    draining = false;
    if (currentQueue.length) {
    queue = currentQueue.concat(queue);
    } else {
    queueIndex = -1;
    }
    if (queue.length) {
    drainQueue();
    }
    }

    function drainQueue() {
    if (draining) {
    return;
    }
    var timeout = setTimeout(cleanUpNextTick);
    draining = true;

    var len = queue.length;
    while(len) {
    currentQueue = queue;
    queue = [];
    while (++queueIndex < len) {
    if (currentQueue) {
    currentQueue[queueIndex].run();
    }
    }
    queueIndex = -1;
    len = queue.length;
    }
    currentQueue = null;
    draining = false;
    clearTimeout(timeout);
    }

    process.nextTick = function (fun) {
    var args = new Array(arguments.length - 1);
    if (arguments.length > 1) {
    for (var i = 1; i < arguments.length; i++) {
    args[i - 1] = arguments[i];
    }
    }
    queue.push(new Item(fun, args));
    if (queue.length === 1 && !draining) {
    setTimeout(drainQueue, 0);
    }
    };

    // v8 likes predictible objects
    function Item(fun, array) {
    this.fun = fun;
    this.array = array;
    }
    Item.prototype.run = function () {
    this.fun.apply(null, this.array);
    };
    process.title = 'browser';
    process.browser = true;
    process.env = {};
    process.argv = [];
    process.version = ''; // empty string to avoid regexp issues
    process.versions = {};

    function noop() {}

    process.on = noop;
    process.addListener = noop;
    process.once = noop;
    process.off = noop;
    process.removeListener = noop;
    process.removeAllListeners = noop;
    process.emit = noop;

    process.binding = function (name) {
    throw new Error('process.binding is not supported');
    };

    process.cwd = function () { return '/' };
    process.chdir = function (dir) {
    throw new Error('process.chdir is not supported');
    };
    process.umask = function() { return 0; };

    },{}]},{},[3])(3)
    });
    432 changes: 0 additions & 432 deletions js/mapbox-gl.js
    0 additions, 432 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
    File renamed without changes.
    File renamed without changes.
  11. fxi revised this gist Sep 1, 2016. No changes.
  12. fxi revised this gist Sep 1, 2016. 5 changed files with 9 additions and 445 deletions.
    183 changes: 0 additions & 183 deletions archive/handleReadJson.js
    Original file line number Diff line number Diff line change
    @@ -1,183 +0,0 @@


    postMessage({
    progress : 0,
    message : "start"
    });


    onmessage = function(e) {
    var dat = e.data;

    // set timing function
    timerVal = 0;
    var timerStart = function() {
    timerVal = new Date();
    };

    var timerLap = function() {
    var lap = new Date() - timerVal;
    timerStart();
    return lap;
    };

    var timerLapString = function() {
    return "( " + timerLap() + " ms)";
    };

    timerStart();

    // Parse json
    console.log(dat);

    var gJson = JSON.parse(dat.json);

    postMessage({
    progress : 55,
    message :" parsed json in " + timerLapString()
    });

    console.log(self);
    // Validation
    var messages = geojsonhint.hint(gJson);
    var errors = [];
    var nErrors = 0;
    var nWarnings = 0;

    messages.forEach(function(x) {
    if (x.level == "warn") {
    nWarnings++;
    } else {
    errors.push(x);
    nErrors++;
    }
    });

    postMessage({
    progress : 60,
    message : " json validation " +
    " n errors=" + nErrors +
    " n warnings =" + nWarnings +
    timerLapString()
    });

    if (errors.length > 0) {
    var msgErrors = errors.length() + "found. check the console for more info";
    postMessage({
    progress : 100,
    message : msgErrors
    });

    console.log(errors);
    alert(msgErrors);
    return ;
    }
    // get extent with geojsonExtent mapbox plugin
    var extent = turf.bbox(gJson);
    //var extent = geojsonExtent(gJson);
    postMessage({
    progress : 80,
    message : " extent found in " + timerLapString()
    });

    // arra of types in data
    var geomTypes = [];

    // Get type for each feature
    gJson.features.forEach(function(x) {
    var g = x.geometry.type;
    if (geomTypes.length === 0 || geomTypes.indexOf(g) == -1) {
    geomTypes.push(g);
    }
    });

    postMessage({
    progress : 90,
    message : " geom types found in " + timerLapString()
    });

    // if more than one type, return an error
    if (geomTypes.length > 1) {
    var msg = "Multi geometry not yet implemented";

    postMessage({
    progress : 100,
    message : msg
    });

    alert(msg);
    return ;
    }

    // Set random id for source and layer
    var id = "mgl_drop_" + geomTypes[0] + "_" + randomString(5);

    // Set random color
    var ran = Math.random();
    var colA = randomHsl(0.6, ran);
    var colB = randomHsl(0.8, ran);

    // Set default type from geojson type
    var typ = typeSwitcher[geomTypes[0]];

    // Set up default style
    var dummyStyle = {
    "circle": {
    "id": id,
    "source": id,
    "type": typ,
    "paint": {
    "circle-color": colA
    }
    },
    "fill": {
    "id": id,
    "source": id,
    "type": typ,
    "paint": {
    "fill-color": colA,
    "fill-outline-color": colB
    }
    },
    "line": {
    "id": id,
    "source": id,
    "type": typ,
    "paint": {
    "line-color": colA,
    "line-width": 10
    }
    }
    };


    postMessage({
    progress : 98,
    message : "Style done."
    });

    return;

    //// Add source and layer;
    //map.fitBounds(extent);

    //map.addSource(id, {
    //type: 'geojson',
    //data: gJson
    //});

    //progressScreen(true, theFile.name, 90, theFile.name + " add layer" + timerLapString());
    //map.addLayer(dummyStyle[typ]);

    //progressScreen(true, theFile.name, 95, theFile.name + " add layer" + timerLapString());

    };

    //var handleReadGeoJson = function(theFile) {

    //return function(e) {
    //}
    //};
    //};


    254 changes: 0 additions & 254 deletions archive/helpersa.js
    Original file line number Diff line number Diff line change
    @@ -1,254 +0,0 @@
    /**
    * Generate a random string of the given length
    * @param {integer} n Number of character
    * @return {string} random string
    */
    var randomString = function(n) {
    var result = "";
    if (!n) n = 5;
    var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for (var i = 0; i < n; i++)
    result += characters.charAt(Math.floor(Math.random() * characters.length));
    return result;
    };
    /**
    * Generate a random hsla color string, with fixed saturation and lightness
    * @param {number} opacity opacity from 0 to 1
    * @param {number} random value from 0 to 1
    * @param {number} saturation from 0 to 100
    * @param {number} lightness from 0 to 100
    */
    var randomHsl = function(opacity, random, saturation, lightness) {
    if (!opacity) opacity = 1;
    if (!saturation) saturation = 100;
    if (!lightness) lightness = 50;
    if (!random) random = Math.random();
    res = "hsla(" + (random * 360) +
    ", " + saturation + "% " +
    ", " + lightness + "% " +
    ", " + opacity + ")";
    return res;
    };

    /**
    * Remove multiple layers by prefix
    * @param {object} map Map object
    * @param {string} prefix Prefix to search for in layers, if something found, remove it
    * @return {array} List of removed layer
    */
    var removeLayersByPrefix = function(map, prefix) {
    var result = [];

    if (map) {
    // no method to get all layers ?
    var layers = map.style._layers;
    for (var l in layers) {
    if (l.indexOf(prefix) > -1) {
    map.removeLayer(l);
    result.push(l);
    }
    }
    }

    return result;
    };




    var progressScreen = function(enable, id, percent, text ) {

    console.log(text);

    lScreen = document.getElementsByClassName("loading-screen")[0];

    if (!enable) {
    if (lScreen) lScreen.remove();
    return;
    }

    if (!id || !percent || !text) return;

    if (!lScreen && enable) {
    lBody = document.getElementsByTagName("body")[0];
    lScreen = document.createElement("div");
    lScreen.className = "loading-screen";
    lScreenContainer = document.createElement("div");
    lScreenContainer.className = "loading-container";
    lScreen.appendChild(lScreenContainer);
    lBody.appendChild(lScreen);
    }

    lItem = document.getElementById(id);
    //lScreenContainer = lScreen.getElementsByClassName("loading-container")[0];
    //lItem = lScreenContainer.querySelector("#" + id);

    if (!lItem) {
    lItem = document.createElement("div");
    lItem.className = "loading-item";
    lItem.setAttribute("id", id);
    pBarIn = document.createElement("div");
    pBarIn.className = "loading-bar-in";
    pBarOut = document.createElement("div");
    pBarOut.className = "loading-bar-out";
    pBarTxt = document.createElement("div");
    pBarTxt.className = "loading-bar-txt";
    pBarOut.appendChild(pBarIn);
    lItem.appendChild(pBarTxt);
    lItem.appendChild(pBarOut);
    lScreenContainer.appendChild(lItem);
    } else {
    pBarIn = lItem.getElementsByClassName("loading-bar-in")[0];
    pBarTxt = lItem.getElementsByClassName("loading-bar-txt")[0];
    }

    if (percent >= 100) {
    lItem = document.getElementById(id);
    if (lItem) lItem.remove();
    } else {
    pBarIn.style.width = percent + "%";
    pBarTxt.innerHTML = text;
    }

    lItems = lScreenContainer.getElementsByClassName("loading-item");

    if (lItems.length === 0) progressScreen(false);

    };









    // geojson type to mapbox gl type
    var typeSwitcher = {
    "Point": "circle",
    "MultiPoint": "line",
    "LineString": "line",
    "MultiLineString": "line",
    "Polygon": "fill",
    "MultiPolygon": "fill",
    "GeometryCollection": "fill"
    };

    // mapbox gl init
    mapboxgl.accessToken = 'pk.eyJ1IjoidW5lcGdyaWQiLCJhIjoiY2lzZnowenUwMDAzdjJubzZyZ3R1bjIzZyJ9.uyP-RWjY-94qCVajU0u8KA';
    var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v9',
    center: [15.6, -4.3],
    zoom: 10
    });



    // test if file api is available
    if (window.File && window.FileReader && window.FileList && window.Blob) {


    // handle read geojson
    // Update progress
    var updateProgress = function(theFile) {
    return function(e) {
    // evt is an ProgressEvent. 100/2 as loading is ~ half the process
    if (e.lengthComputable) {
    var percentLoaded = Math.round((e.loaded / e.total) * 50);
    progressScreen(true, theFile.name, percentLoaded, theFile.name + " loading (" + percentLoaded * 2 + "%)");
    }
    };
    };

    var startProgress = function(theFile) {
    return function(e) {
    progressScreen(true, theFile.name, 1, theFile.name + " init .. ");
    };
    };
    var errorProgress = function(theFile) {
    return function(e) {
    progressScreen(true, theFile.name, 100, theFile.name + "stop .. ");
    };
    };


    var startWorker = function(theFile){
    return function(e){

    var res = {
    json : e.target.result,
    fileName : theFile.name
    };

    var w = new Worker("handleReadJson.js");
    w.onmessage = function(e){
    var m = e.data;
    progressScreen(true, theFile.name,m.progress, theFile.name + ":" + m.message );
    if(m.progress == 100){
    w.terminate();
    }
    };
    w.postMessage(res);
    };
    };

    var updateLayerList = function(theFile) {
    return function(e) {
    progressScreen(true, theFile.name, 100, theFile.name + " Done. ");
    };
    };
    // handle drop event
    var handleDropGeojson = function(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    var files = evt.dataTransfer.files;

    var nFiles = files.length;
    var incFile = 100 / nFiles;
    var progressBar = 0;

    // In case of multiple file, loop on them
    for (var i = 0; i < nFiles; i++) {

    f = files[i];

    // Only process geojson files. Validate later.
    if (f.name.indexOf(".geojson") == -1) {
    continue;
    }






    var reader = new FileReader();
    reader.onloadstart = (startProgress)(f);
    reader.onprogress = (updateProgress)(f);
    reader.onerror = (errorProgress)(f);
    //reader.onload = (handleReadGeoJson)(f);
    reader.onload = (startWorker)(f);
    reader.onloadend = (updateLayerList)(f);

    // read the geojson
    reader.readAsText(f);

    }
    };
    var handleDragOver = function(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
    };

    // Set events
    mapEl = document.getElementById("map");
    mapEl.addEventListener('dragover', handleDragOver, false);
    mapEl.addEventListener('drop', handleDropGeojson, false);


    } else {
    alert('The File APIs are not fully supported in this browser.');
    }
    4 changes: 4 additions & 0 deletions css/app.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,4 @@
    body { margin:0; padding:0; }
    #map { position:absolute; top:0; bottom:0; width:100%; }


    4 changes: 0 additions & 4 deletions css/style.css → css/progress.css
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,3 @@
    body { margin:0; padding:0; }
    #map { position:absolute; top:0; bottom:0; width:100%; }


    .loading-screen {
    background-color : rgba(47,47,47,0.6);
    @@ -12,7 +9,6 @@ body { margin:0; padding:0; }
    }

    .loading-container {
    /*font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;*/
    font-family : "Lucida Console", Monaco, monospace;
    color: #0f0;
    position:absolute;
    9 changes: 5 additions & 4 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -2,15 +2,16 @@
    <html>
    <head>
    <meta charset='utf-8' />
    <title></title>
    <title>Drop geojson</title>
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <meta http-equiv="cache-control" content="Public">
    <link href='css/mapbox-gl.css' rel='stylesheet' />
    <link href='css/style.css' rel='stylesheet' />
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.23.0/mapbox-gl.css' rel='stylesheet' />
    <link href='css/progress.css' rel='stylesheet' />
    <link href='css/app.css' rel='stylesheet' />
    </head>
    <body>
    <div id='map'></div>
    <script src='js/mapbox-gl.js'></script>
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.23.0/mapbox-gl.js'></script>
    <script src='js/helpers.js'></script>
    <script src='js/app.js'></script>
    </body>
  13. fxi revised this gist Sep 1, 2016. 11 changed files with 3307 additions and 185 deletions.
    183 changes: 183 additions & 0 deletions archive/handleReadJson.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,183 @@


    postMessage({
    progress : 0,
    message : "start"
    });


    onmessage = function(e) {
    var dat = e.data;

    // set timing function
    timerVal = 0;
    var timerStart = function() {
    timerVal = new Date();
    };

    var timerLap = function() {
    var lap = new Date() - timerVal;
    timerStart();
    return lap;
    };

    var timerLapString = function() {
    return "( " + timerLap() + " ms)";
    };

    timerStart();

    // Parse json
    console.log(dat);

    var gJson = JSON.parse(dat.json);

    postMessage({
    progress : 55,
    message :" parsed json in " + timerLapString()
    });

    console.log(self);
    // Validation
    var messages = geojsonhint.hint(gJson);
    var errors = [];
    var nErrors = 0;
    var nWarnings = 0;

    messages.forEach(function(x) {
    if (x.level == "warn") {
    nWarnings++;
    } else {
    errors.push(x);
    nErrors++;
    }
    });

    postMessage({
    progress : 60,
    message : " json validation " +
    " n errors=" + nErrors +
    " n warnings =" + nWarnings +
    timerLapString()
    });

    if (errors.length > 0) {
    var msgErrors = errors.length() + "found. check the console for more info";
    postMessage({
    progress : 100,
    message : msgErrors
    });

    console.log(errors);
    alert(msgErrors);
    return ;
    }
    // get extent with geojsonExtent mapbox plugin
    var extent = turf.bbox(gJson);
    //var extent = geojsonExtent(gJson);
    postMessage({
    progress : 80,
    message : " extent found in " + timerLapString()
    });

    // arra of types in data
    var geomTypes = [];

    // Get type for each feature
    gJson.features.forEach(function(x) {
    var g = x.geometry.type;
    if (geomTypes.length === 0 || geomTypes.indexOf(g) == -1) {
    geomTypes.push(g);
    }
    });

    postMessage({
    progress : 90,
    message : " geom types found in " + timerLapString()
    });

    // if more than one type, return an error
    if (geomTypes.length > 1) {
    var msg = "Multi geometry not yet implemented";

    postMessage({
    progress : 100,
    message : msg
    });

    alert(msg);
    return ;
    }

    // Set random id for source and layer
    var id = "mgl_drop_" + geomTypes[0] + "_" + randomString(5);

    // Set random color
    var ran = Math.random();
    var colA = randomHsl(0.6, ran);
    var colB = randomHsl(0.8, ran);

    // Set default type from geojson type
    var typ = typeSwitcher[geomTypes[0]];

    // Set up default style
    var dummyStyle = {
    "circle": {
    "id": id,
    "source": id,
    "type": typ,
    "paint": {
    "circle-color": colA
    }
    },
    "fill": {
    "id": id,
    "source": id,
    "type": typ,
    "paint": {
    "fill-color": colA,
    "fill-outline-color": colB
    }
    },
    "line": {
    "id": id,
    "source": id,
    "type": typ,
    "paint": {
    "line-color": colA,
    "line-width": 10
    }
    }
    };


    postMessage({
    progress : 98,
    message : "Style done."
    });

    return;

    //// Add source and layer;
    //map.fitBounds(extent);

    //map.addSource(id, {
    //type: 'geojson',
    //data: gJson
    //});

    //progressScreen(true, theFile.name, 90, theFile.name + " add layer" + timerLapString());
    //map.addLayer(dummyStyle[typ]);

    //progressScreen(true, theFile.name, 95, theFile.name + " add layer" + timerLapString());

    };

    //var handleReadGeoJson = function(theFile) {

    //return function(e) {
    //}
    //};
    //};


    254 changes: 254 additions & 0 deletions archive/helpersa.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,254 @@
    /**
    * Generate a random string of the given length
    * @param {integer} n Number of character
    * @return {string} random string
    */
    var randomString = function(n) {
    var result = "";
    if (!n) n = 5;
    var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for (var i = 0; i < n; i++)
    result += characters.charAt(Math.floor(Math.random() * characters.length));
    return result;
    };
    /**
    * Generate a random hsla color string, with fixed saturation and lightness
    * @param {number} opacity opacity from 0 to 1
    * @param {number} random value from 0 to 1
    * @param {number} saturation from 0 to 100
    * @param {number} lightness from 0 to 100
    */
    var randomHsl = function(opacity, random, saturation, lightness) {
    if (!opacity) opacity = 1;
    if (!saturation) saturation = 100;
    if (!lightness) lightness = 50;
    if (!random) random = Math.random();
    res = "hsla(" + (random * 360) +
    ", " + saturation + "% " +
    ", " + lightness + "% " +
    ", " + opacity + ")";
    return res;
    };

    /**
    * Remove multiple layers by prefix
    * @param {object} map Map object
    * @param {string} prefix Prefix to search for in layers, if something found, remove it
    * @return {array} List of removed layer
    */
    var removeLayersByPrefix = function(map, prefix) {
    var result = [];

    if (map) {
    // no method to get all layers ?
    var layers = map.style._layers;
    for (var l in layers) {
    if (l.indexOf(prefix) > -1) {
    map.removeLayer(l);
    result.push(l);
    }
    }
    }

    return result;
    };




    var progressScreen = function(enable, id, percent, text ) {

    console.log(text);

    lScreen = document.getElementsByClassName("loading-screen")[0];

    if (!enable) {
    if (lScreen) lScreen.remove();
    return;
    }

    if (!id || !percent || !text) return;

    if (!lScreen && enable) {
    lBody = document.getElementsByTagName("body")[0];
    lScreen = document.createElement("div");
    lScreen.className = "loading-screen";
    lScreenContainer = document.createElement("div");
    lScreenContainer.className = "loading-container";
    lScreen.appendChild(lScreenContainer);
    lBody.appendChild(lScreen);
    }

    lItem = document.getElementById(id);
    //lScreenContainer = lScreen.getElementsByClassName("loading-container")[0];
    //lItem = lScreenContainer.querySelector("#" + id);

    if (!lItem) {
    lItem = document.createElement("div");
    lItem.className = "loading-item";
    lItem.setAttribute("id", id);
    pBarIn = document.createElement("div");
    pBarIn.className = "loading-bar-in";
    pBarOut = document.createElement("div");
    pBarOut.className = "loading-bar-out";
    pBarTxt = document.createElement("div");
    pBarTxt.className = "loading-bar-txt";
    pBarOut.appendChild(pBarIn);
    lItem.appendChild(pBarTxt);
    lItem.appendChild(pBarOut);
    lScreenContainer.appendChild(lItem);
    } else {
    pBarIn = lItem.getElementsByClassName("loading-bar-in")[0];
    pBarTxt = lItem.getElementsByClassName("loading-bar-txt")[0];
    }

    if (percent >= 100) {
    lItem = document.getElementById(id);
    if (lItem) lItem.remove();
    } else {
    pBarIn.style.width = percent + "%";
    pBarTxt.innerHTML = text;
    }

    lItems = lScreenContainer.getElementsByClassName("loading-item");

    if (lItems.length === 0) progressScreen(false);

    };









    // geojson type to mapbox gl type
    var typeSwitcher = {
    "Point": "circle",
    "MultiPoint": "line",
    "LineString": "line",
    "MultiLineString": "line",
    "Polygon": "fill",
    "MultiPolygon": "fill",
    "GeometryCollection": "fill"
    };

    // mapbox gl init
    mapboxgl.accessToken = 'pk.eyJ1IjoidW5lcGdyaWQiLCJhIjoiY2lzZnowenUwMDAzdjJubzZyZ3R1bjIzZyJ9.uyP-RWjY-94qCVajU0u8KA';
    var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v9',
    center: [15.6, -4.3],
    zoom: 10
    });



    // test if file api is available
    if (window.File && window.FileReader && window.FileList && window.Blob) {


    // handle read geojson
    // Update progress
    var updateProgress = function(theFile) {
    return function(e) {
    // evt is an ProgressEvent. 100/2 as loading is ~ half the process
    if (e.lengthComputable) {
    var percentLoaded = Math.round((e.loaded / e.total) * 50);
    progressScreen(true, theFile.name, percentLoaded, theFile.name + " loading (" + percentLoaded * 2 + "%)");
    }
    };
    };

    var startProgress = function(theFile) {
    return function(e) {
    progressScreen(true, theFile.name, 1, theFile.name + " init .. ");
    };
    };
    var errorProgress = function(theFile) {
    return function(e) {
    progressScreen(true, theFile.name, 100, theFile.name + "stop .. ");
    };
    };


    var startWorker = function(theFile){
    return function(e){

    var res = {
    json : e.target.result,
    fileName : theFile.name
    };

    var w = new Worker("handleReadJson.js");
    w.onmessage = function(e){
    var m = e.data;
    progressScreen(true, theFile.name,m.progress, theFile.name + ":" + m.message );
    if(m.progress == 100){
    w.terminate();
    }
    };
    w.postMessage(res);
    };
    };

    var updateLayerList = function(theFile) {
    return function(e) {
    progressScreen(true, theFile.name, 100, theFile.name + " Done. ");
    };
    };
    // handle drop event
    var handleDropGeojson = function(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    var files = evt.dataTransfer.files;

    var nFiles = files.length;
    var incFile = 100 / nFiles;
    var progressBar = 0;

    // In case of multiple file, loop on them
    for (var i = 0; i < nFiles; i++) {

    f = files[i];

    // Only process geojson files. Validate later.
    if (f.name.indexOf(".geojson") == -1) {
    continue;
    }






    var reader = new FileReader();
    reader.onloadstart = (startProgress)(f);
    reader.onprogress = (updateProgress)(f);
    reader.onerror = (errorProgress)(f);
    //reader.onload = (handleReadGeoJson)(f);
    reader.onload = (startWorker)(f);
    reader.onloadend = (updateLayerList)(f);

    // read the geojson
    reader.readAsText(f);

    }
    };
    var handleDragOver = function(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
    };

    // Set events
    mapEl = document.getElementById("map");
    mapEl.addEventListener('dragover', handleDragOver, false);
    mapEl.addEventListener('drop', handleDropGeojson, false);


    } else {
    alert('The File APIs are not fully supported in this browser.');
    }
    268 changes: 268 additions & 0 deletions css/mapbox-gl.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,268 @@
    .mapboxgl-map {
    font: 12px/20px 'Helvetica Neue', Arial, Helvetica, sans-serif;
    overflow: hidden;
    position: relative;
    -webkit-tap-highlight-color: rgba(0,0,0,0);
    }

    .mapboxgl-canvas-container.mapboxgl-interactive,
    .mapboxgl-ctrl-nav-compass {
    cursor: -webkit-grab;
    cursor: -moz-grab;
    cursor: grab;
    }
    .mapboxgl-canvas-container.mapboxgl-interactive:active,
    .mapboxgl-ctrl-nav-compass:active {
    cursor: -webkit-grabbing;
    cursor: -moz-grabbing;
    cursor: grabbing;
    }

    .mapboxgl-ctrl-top-left,
    .mapboxgl-ctrl-top-right,
    .mapboxgl-ctrl-bottom-left,
    .mapboxgl-ctrl-bottom-right { position:absolute; pointer-events:none; z-index:2; }
    .mapboxgl-ctrl-top-left { top:0; left:0; }
    .mapboxgl-ctrl-top-right { top:0; right:0; }
    .mapboxgl-ctrl-bottom-left { bottom:0; left:0; }
    .mapboxgl-ctrl-bottom-right { right:0; bottom:0; }

    .mapboxgl-ctrl { clear:both; pointer-events:auto }
    .mapboxgl-ctrl-top-left .mapboxgl-ctrl { margin:10px 0 0 10px; float:left; }
    .mapboxgl-ctrl-top-right .mapboxgl-ctrl{ margin:10px 10px 0 0; float:right; }
    .mapboxgl-ctrl-bottom-left .mapboxgl-ctrl { margin:0 0 10px 10px; float:left; }
    .mapboxgl-ctrl-bottom-right .mapboxgl-ctrl { margin:0 10px 10px 0; float:right; }

    .mapboxgl-ctrl-group {
    border-radius: 4px;
    -moz-box-shadow: 0px 0px 2px rgba(0,0,0,0.1);
    -webkit-box-shadow: 0px 0px 2px rgba(0,0,0,0.1);
    box-shadow: 0px 0px 0px 2px rgba(0,0,0,0.1);
    overflow: hidden;
    background: #fff;
    }
    .mapboxgl-ctrl-group > button {
    width: 30px;
    height: 30px;
    display: block;
    padding: 0;
    outline: none;
    border: none;
    border-bottom: 1px solid #ddd;
    box-sizing: border-box;
    background-color: rgba(0,0,0,0);
    cursor: pointer;
    }
    /* https://bugzilla.mozilla.org/show_bug.cgi?id=140562 */
    .mapboxgl-ctrl > button::-moz-focus-inner {
    border: 0;
    padding: 0;
    }
    .mapboxgl-ctrl > button:last-child {
    border-bottom: 0;
    }
    .mapboxgl-ctrl > button:hover {
    background-color: rgba(0,0,0,0.05);
    }
    .mapboxgl-ctrl-icon,
    .mapboxgl-ctrl-icon > div.arrow {
    speak: none;
    -webkit-font-smoothing: antialiased;
    -moz-osx-font-smoothing: grayscale;
    }
    .mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-out {
    padding: 5px;
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27m%207%2C9%20c%20-0.554%2C0%20-1%2C0.446%20-1%2C1%200%2C0.554%200.446%2C1%201%2C1%20l%206%2C0%20c%200.554%2C0%201%2C-0.446%201%2C-1%200%2C-0.554%20-0.446%2C-1%20-1%2C-1%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A");
    }
    .mapboxgl-ctrl-icon.mapboxgl-ctrl-zoom-in {
    padding: 5px;
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20viewBox%3D%270%200%2020%2020%27%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%3E%0A%20%20%3Cpath%20style%3D%27fill%3A%23333333%3B%27%20d%3D%27M%2010%206%20C%209.446%206%209%206.4459904%209%207%20L%209%209%20L%207%209%20C%206.446%209%206%209.446%206%2010%20C%206%2010.554%206.446%2011%207%2011%20L%209%2011%20L%209%2013%20C%209%2013.55401%209.446%2014%2010%2014%20C%2010.554%2014%2011%2013.55401%2011%2013%20L%2011%2011%20L%2013%2011%20C%2013.554%2011%2014%2010.554%2014%2010%20C%2014%209.446%2013.554%209%2013%209%20L%2011%209%20L%2011%207%20C%2011%206.4459904%2010.554%206%2010%206%20z%27%20%2F%3E%0A%3C%2Fsvg%3E%0A");
    }
    .mapboxgl-ctrl-icon.mapboxgl-ctrl-geolocate {
    padding: 5px;
    background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxuczpzdmc9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMCAyMCIgdmVyc2lvbj0iMS4xIj48cGF0aCBkPSJNMTAgNEM5IDQgOSA1IDkgNUw5IDUuMUE1IDUgMCAwIDAgNS4xIDlMNSA5QzUgOSA0IDkgNCAxMCA0IDExIDUgMTEgNSAxMUw1LjEgMTFBNSA1IDAgMCAwIDkgMTQuOUw5IDE1QzkgMTUgOSAxNiAxMCAxNiAxMSAxNiAxMSAxNSAxMSAxNUwxMSAxNC45QTUgNSAwIDAgMCAxNC45IDExTDE1IDExQzE1IDExIDE2IDExIDE2IDEwIDE2IDkgMTUgOSAxNSA5TDE0LjkgOUE1IDUgMCAwIDAgMTEgNS4xTDExIDVDMTEgNSAxMSA0IDEwIDR6TTEwIDYuNUEzLjUgMy41IDAgMCAxIDEzLjUgMTAgMy41IDMuNSAwIDAgMSAxMCAxMy41IDMuNSAzLjUgMCAwIDEgNi41IDEwIDMuNSAzLjUgMCAwIDEgMTAgNi41ek0xMCA4LjNBMS44IDEuOCAwIDAgMCA4LjMgMTAgMS44IDEuOCAwIDAgMCAxMCAxMS44IDEuOCAxLjggMCAwIDAgMTEuOCAxMCAxLjggMS44IDAgMCAwIDEwIDguM3oiIGZpbGw9IiMzMzMiLz48L3N2Zz4=");
    }

    .mapboxgl-ctrl-icon.mapboxgl-ctrl-compass > div.arrow {
    width: 20px;
    height: 20px;
    margin: 5px;
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%0A%09%3Cpolygon%20fill%3D%27%23333333%27%20points%3D%276%2C9%2010%2C1%2014%2C9%27%2F%3E%0A%09%3Cpolygon%20fill%3D%27%23CCCCCC%27%20points%3D%276%2C11%2010%2C19%2014%2C11%20%27%2F%3E%0A%3C%2Fsvg%3E");
    background-repeat: no-repeat;
    }

    .mapboxgl-ctrl.mapboxgl-ctrl-attrib {
    padding: 0 5px;
    background-color: rgba(255,255,255,0.5);
    margin: 0;
    }
    .mapboxgl-ctrl-attrib a {
    color: rgba(0,0,0,0.75);
    text-decoration: none;
    }
    .mapboxgl-ctrl-attrib a:hover {
    color: inherit;
    text-decoration: underline;
    }
    .mapboxgl-ctrl-attrib .mapbox-improve-map {
    font-weight: bold;
    margin-left: 2px;
    }

    .mapboxgl-ctrl-scale {
    background-color: rgba(255,255,255,0.75);
    font-size: 10px;
    border-width: medium 2px 2px;
    border-style: none solid solid;
    border-color: #333;
    padding: 0 5px;
    color: #333;
    }

    .mapboxgl-popup {
    position: absolute;
    top: 0;
    left: 0;
    display: -webkit-flex;
    display: flex;
    will-change: transform;
    pointer-events: none;
    }
    .mapboxgl-popup-anchor-top,
    .mapboxgl-popup-anchor-top-left,
    .mapboxgl-popup-anchor-top-right {
    -webkit-flex-direction: column;
    flex-direction: column;
    }
    .mapboxgl-popup-anchor-bottom,
    .mapboxgl-popup-anchor-bottom-left,
    .mapboxgl-popup-anchor-bottom-right {
    -webkit-flex-direction: column-reverse;
    flex-direction: column-reverse;
    }
    .mapboxgl-popup-anchor-left {
    -webkit-flex-direction: row;
    flex-direction: row;
    }
    .mapboxgl-popup-anchor-right {
    -webkit-flex-direction: row-reverse;
    flex-direction: row-reverse;
    }
    .mapboxgl-popup-tip {
    width: 0;
    height: 0;
    border: 10px solid transparent;
    z-index: 1;
    }
    .mapboxgl-popup-anchor-top .mapboxgl-popup-tip {
    -webkit-align-self: center;
    align-self: center;
    border-top: none;
    border-bottom-color: #fff;
    }
    .mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip {
    -webkit-align-self: flex-start;
    align-self: flex-start;
    border-top: none;
    border-left: none;
    border-bottom-color: #fff;
    }
    .mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip {
    -webkit-align-self: flex-end;
    align-self: flex-end;
    border-top: none;
    border-right: none;
    border-bottom-color: #fff;
    }
    .mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip {
    -webkit-align-self: center;
    align-self: center;
    border-bottom: none;
    border-top-color: #fff;
    }
    .mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip {
    -webkit-align-self: flex-start;
    align-self: flex-start;
    border-bottom: none;
    border-left: none;
    border-top-color: #fff;
    }
    .mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip {
    -webkit-align-self: flex-end;
    align-self: flex-end;
    border-bottom: none;
    border-right: none;
    border-top-color: #fff;
    }
    .mapboxgl-popup-anchor-left .mapboxgl-popup-tip {
    -webkit-align-self: center;
    align-self: center;
    border-left: none;
    border-right-color: #fff;
    }
    .mapboxgl-popup-anchor-right .mapboxgl-popup-tip {
    -webkit-align-self: center;
    align-self: center;
    border-right: none;
    border-left-color: #fff;
    }
    .mapboxgl-popup-close-button {
    position: absolute;
    right: 0;
    top: 0;
    border: none;
    border-radius: 0 3px 0 0;
    cursor: pointer;
    background-color: rgba(0,0,0,0);
    }
    .mapboxgl-popup-close-button:hover {
    background-color: rgba(0,0,0,0.05);
    }
    .mapboxgl-popup-content {
    position: relative;
    background: #fff;
    border-radius: 3px;
    box-shadow: 0 1px 2px rgba(0,0,0,0.10);
    padding: 10px 10px 15px;
    pointer-events: auto;
    }
    .mapboxgl-popup-anchor-top-left .mapboxgl-popup-content {
    border-top-left-radius: 0;
    }
    .mapboxgl-popup-anchor-top-right .mapboxgl-popup-content {
    border-top-right-radius: 0;
    }
    .mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-content {
    border-bottom-left-radius: 0;
    }
    .mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-content {
    border-bottom-right-radius: 0;
    }

    .mapboxgl-marker {
    position: absolute;
    top: 0;
    left: 0;
    will-change: transform;
    }

    .mapboxgl-crosshair,
    .mapboxgl-crosshair .mapboxgl-interactive,
    .mapboxgl-crosshair .mapboxgl-interactive:active {
    cursor: crosshair;
    }
    .mapboxgl-boxzoom {
    position: absolute;
    top: 0;
    left: 0;
    width: 0;
    height: 0;
    background: #fff;
    border: 2px dotted #202020;
    opacity: 0.5;
    }
    @media print {
    .mapbox-improve-map {
    display:none;
    }
    }
    44 changes: 44 additions & 0 deletions css/style.css
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,44 @@
    body { margin:0; padding:0; }
    #map { position:absolute; top:0; bottom:0; width:100%; }


    .loading-screen {
    background-color : rgba(47,47,47,0.6);
    top: 0px;
    width: 100%;
    bottom:0px;
    display: block;
    position:absolute;
    }

    .loading-container {
    /*font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;*/
    font-family : "Lucida Console", Monaco, monospace;
    color: #0f0;
    position:absolute;
    width: 40%;
    left: 50%;
    padding: 10px;
    }

    .loading-item {
    padding: 5px;
    width: 99%;
    }

    .loading-bar-out {
    height:5px;
    width:100%;
    border:1px solid #0f0;
    }

    .loading-bar-in {
    width:0px;
    height:100%;
    background-color: #0f0;
    }

    .loading-bar-txt {
    font-size: 14px;
    }

    191 changes: 6 additions & 185 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -4,193 +4,14 @@
    <meta charset='utf-8' />
    <title></title>
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.23.0/mapbox-gl.js'></script>
    <script src='https://cdn.rawgit.com/mapbox/geojsonhint/master/geojsonhint.js'></script>
    <script src='https://cdn.rawgit.com/mapbox/geojson-extent/master/geojson-extent.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.23.0/mapbox-gl.css' rel='stylesheet' />

    <style>
    body { margin:0; padding:0; }
    #map { position:absolute; top:0; bottom:0; width:100%; }
    </style>
    <meta http-equiv="cache-control" content="Public">
    <link href='css/mapbox-gl.css' rel='stylesheet' />
    <link href='css/style.css' rel='stylesheet' />
    </head>
    <body>

    <div id='map'></div>
    <script>

    /**
    * Generate a random string of the given length
    * @param {integer} n Number of character
    * @return {string} random string
    */
    var randomString = function (n) {
    var result = "";
    if( !n ) n=5;
    var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for( var i=0; i < n; i++ )
    result += characters.charAt(Math.floor(Math.random() * characters.length));
    return result;
    };
    /**
    * Generate a random hsla color string, with fixed saturation and lightness
    * @param {number} opacity opacity from 0 to 1
    * @param {number} random value from 0 to 1
    * @param {number} saturation from 0 to 100
    * @param {number} lightness from 0 to 100
    */
    var randomHsl = function (opacity, random, saturation, lightness) {
    if (!opacity) opacity = 1;
    if (!saturation) saturation = 100;
    if (!lightness) lightness = 50;
    if (!random) random = Math.random();
    res = "hsla(" + (random * 360) +
    ", " + saturation + "% " +
    ", " + lightness + "% " +
    ", " + opacity + ")";
    return res;
    };

    // geojson type to mapbox gl type
    var typeSwitcher = {
    "Point":"circle",
    "MultiPoint":"line",
    "LineString":"line",
    "MultiLineString":"line",
    "Polygon":"fill",
    "MultiyPolygon":"fill",
    "GeometryCollection":"fill"
    };

    // mapbox gl init
    mapboxgl.accessToken = 'pk.eyJ1IjoidW5lcGdyaWQiLCJhIjoiY2lzZnowenUwMDAzdjJubzZyZ3R1bjIzZyJ9.uyP-RWjY-94qCVajU0u8KA';
    var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v9',
    center: [15.6,-4.3],
    zoom: 10
    });

    // test if file api is available
    if (window.File && window.FileReader && window.FileList && window.Blob) {
    function handleDropGeojson(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    var files = evt.dataTransfer.files;

    // In case of multiple file, loop on them
    for (var i = 0, f; f = files[i]; i++) {

    // Only process geojson files. Validate later.
    if(f.name.indexOf(".geojson")==-1){
    continue;
    }

    var reader = new FileReader();

    reader.onload = (function(theFile) {
    return function(e) {
    gJson = JSON.parse(e.target.result);
    // handle simplified geojson validation
    var errors = geojsonhint.hint(gJson);
    errors.forEach(function(x){
    if(x.level=="warn"){
    console.log(x.message)
    }else{
    alert(x.message)
    throw(x.message)
    }
    })

    // get extent with geojsonExtent mapbox plugin
    var extent = geojsonExtent(gJson);

    // arra of types in data
    var geomTypes = [] ;

    // Get type for each feature
    gJson.features.forEach(function(x){
    var g = x.geometry.type;
    if( geomTypes.length == 0 || geomTypes.indexOf(g) == -1 ){
    geomTypes.push(g);
    };
    });
    if( geomTypes.length > 1){
    var msg = "Multi geometry not yet implemented";
    alert(msg);
    throw(msg);
    }

    // Set random id for source and layer
    var id = "mgl_drop_" + geomTypes[0] + "_" + randomString(5);

    // Set random color
    var ran = Math.random();
    var colA = randomHsl( 0.6, ran );
    var colB = randomHsl( 0.8, ran );

    // Set default type from geojson type
    var typ = typeSwitcher[geomTypes[0]];

    // Set up default style
    var dummyStyle = {
    "circle": {
    "id" : id,
    "source":id,
    "type" : typ,
    "paint" : {
    "circle-color" : colA
    }
    },
    "fill":{
    "id":id,
    "source":id,
    "type":typ,
    "paint":{
    "fill-color":colA,
    "fill-outline-color":colB
    }
    },
    "line":{
    "id":id,
    "source":id,
    "type":typ,
    "paint":{
    "line-color":colA,
    "line-width":10
    }
    }
    }

    // Add source and layer;
    map.fitBounds(extent);
    map.addSource(id,{type:'geojson',data:gJson});
    map.addLayer(dummyStyle[typ]);
    };
    })(f);

    reader.readAsText(f);
    }
    }

    function handleDragOver(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
    }

    // Set events
    mapEl = document.getElementById("map");
    mapEl.addEventListener('dragover', handleDragOver, false);
    mapEl.addEventListener('drop', handleDropGeojson, false);


    } else {
    alert('The File APIs are not fully supported in this browser.');
    }


    </script>

    <script src='js/mapbox-gl.js'></script>
    <script src='js/helpers.js'></script>
    <script src='js/app.js'></script>
    </body>
    </html>
    175 changes: 175 additions & 0 deletions js/app.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,175 @@
    // mapbox gl init
    mapboxgl.accessToken = 'pk.eyJ1IjoidW5lcGdyaWQiLCJhIjoiY2lzZnowenUwMDAzdjJubzZyZ3R1bjIzZyJ9.uyP-RWjY-94qCVajU0u8KA';
    var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v9',
    center: [15.6, -4.3],
    zoom: 10
    });


    var data = {};



    // test if file api is available
    if (window.File && window.FileReader && window.FileList && window.Blob) {


    // handle read geojson
    // Update progress
    var updateProgress = function(theFile) {
    return function(e) {
    // evt is an ProgressEvent. 100/2 as loading is ~ half the process
    if (e.lengthComputable) {
    var percentLoaded = Math.round((e.loaded / e.total) * 50);
    progressScreen(
    true,
    theFile.name,
    percentLoaded,
    theFile.name + " loading (" + percentLoaded + "%)"
    );
    }
    };
    };
    // init progress bar
    var startProgress = function(theFile) {
    return function(e) {
    progressScreen(
    true,
    theFile.name,
    0,
    theFile.name + " init .. "
    );
    };
    };
    // on error, set progress to 100 (remove it)
    var errorProgress = function(theFile) {
    return function(e) {
    progressScreen(
    true,
    theFile.name,
    100,
    theFile.name + "stop .. "
    );
    };
    };

    // handle worker
    var startWorker = function(theFile) {
    return function(e) {
    // Create a worker to handle this file
    var w = new Worker("js/handleReadJson.js");

    // parse file content before passing to worker.
    var gJson = JSON.parse(e.target.result);

    // Message to pass to the worker
    var res = {
    json: gJson,
    fileName: theFile.name
    };

    // handle message received
    w.onmessage = function(e) {
    var m = e.data;
    if ( m.progress ) {
    progressScreen(
    true,
    theFile.name,
    m.progress,
    theFile.name + ":" + m.message
    );
    }

    // send alert for errors message
    if( m.errorMessage ){
    alert(m.errorMessage);
    }

    // If extent is received
    if (m.extent) {
    map.fitBounds(m.extent);
    }

    // If layer is valid and returned
    if (m.layer) {
    try {
    progressScreen(
    true,
    theFile.name,
    100,
    theFile.name + " done"
    );
    // add source to map
    map.addSource(m.id, {
    "type": "geojson",
    "data": gJson
    });
    // add layer
    map.addLayer(m.layer);
    // set progress to max
    data[m.id] = gJson;
    }
    catch(err){
    alert(err);
    }
    // close worker
    w.terminate();
    }

    };
    // launch process
    w.postMessage(res);
    };
    };

    var updateLayerList = function(theFile) {
    return function(e) {};
    };
    // handle drop event
    var handleDropGeojson = function(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    var files = evt.dataTransfer.files;

    var nFiles = files.length;
    var incFile = 100 / nFiles;
    var progressBar = 0;

    // In case of multiple file, loop on them
    for (var i = 0; i < nFiles; i++) {

    f = files[i];

    // Only process geojson files. Validate later.
    if (f.name.indexOf(".geojson") == -1) {
    continue;
    }
    // get a new reader
    var reader = new FileReader();
    // handle events
    reader.onloadstart = (startProgress)(f);
    reader.onprogress = (updateProgress)(f);
    reader.onerror = (errorProgress)(f);
    reader.onload = (startWorker)(f);
    reader.onloadend = (updateLayerList)(f);
    // read the geojson
    reader.readAsText(f);
    }
    };
    var handleDragOver = function(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
    };

    // Set events
    mapEl = document.getElementById("map");
    mapEl.addEventListener('dragover', handleDragOver, false);
    mapEl.addEventListener('drop', handleDropGeojson, false);


    } else {
    alert('The File APIs are not fully supported in this browser.');
    }
    1,566 changes: 1,566 additions & 0 deletions js/geojsonhint.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,1566 @@
    (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.geojsonhint = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
    (function (process){
    /* parser generated by jison 0.4.17 */
    /*
    Returns a Parser object of the following structure:
    Parser: {
    yy: {}
    }
    Parser.prototype: {
    yy: {},
    trace: function(),
    symbols_: {associative list: name ==> number},
    terminals_: {associative list: number ==> name},
    productions_: [...],
    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
    table: [...],
    defaultActions: {...},
    parseError: function(str, hash),
    parse: function(input),
    lexer: {
    EOF: 1,
    parseError: function(str, hash),
    setInput: function(input),
    input: function(),
    unput: function(str),
    more: function(),
    less: function(n),
    pastInput: function(),
    upcomingInput: function(),
    showPosition: function(),
    test_match: function(regex_match_array, rule_index),
    next: function(),
    lex: function(),
    begin: function(condition),
    popState: function(),
    _currentRules: function(),
    topState: function(),
    pushState: function(condition),
    options: {
    ranges: boolean (optional: true ==> token location info will include a .range[] member)
    flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
    backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
    },
    performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
    rules: [...],
    conditions: {associative list: name ==> set},
    }
    }
    token location info (@$, _$, etc.): {
    first_line: n,
    last_line: n,
    first_column: n,
    last_column: n,
    range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
    }
    the parseError function receives a 'hash' object with these members for lexer and parser errors: {
    text: (matched text)
    token: (the produced terminal token, if any)
    line: (yylineno)
    }
    while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
    loc: (yylloc)
    expected: (string describing the set of expected tokens)
    recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
    }
    */
    var jsonlint = (function(){
    var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,12],$V1=[1,13],$V2=[1,9],$V3=[1,10],$V4=[1,11],$V5=[1,14],$V6=[1,15],$V7=[14,18,22,24],$V8=[18,22],$V9=[22,24];
    var parser = {trace: function trace() { },
    yy: {},
    symbols_: {"error":2,"JSONString":3,"STRING":4,"JSONNumber":5,"NUMBER":6,"JSONNullLiteral":7,"NULL":8,"JSONBooleanLiteral":9,"TRUE":10,"FALSE":11,"JSONText":12,"JSONValue":13,"EOF":14,"JSONObject":15,"JSONArray":16,"{":17,"}":18,"JSONMemberList":19,"JSONMember":20,":":21,",":22,"[":23,"]":24,"JSONElementList":25,"$accept":0,"$end":1},
    terminals_: {2:"error",4:"STRING",6:"NUMBER",8:"NULL",10:"TRUE",11:"FALSE",14:"EOF",17:"{",18:"}",21:":",22:",",23:"[",24:"]"},
    productions_: [0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]],
    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
    /* this == yyval */

    var $0 = $$.length - 1;
    switch (yystate) {
    case 1:
    // replace escaped characters with actual character
    this.$ = yytext.replace(/\\(\\|")/g, "$"+"1")
    .replace(/\\n/g,'\n')
    .replace(/\\r/g,'\r')
    .replace(/\\t/g,'\t')
    .replace(/\\v/g,'\v')
    .replace(/\\f/g,'\f')
    .replace(/\\b/g,'\b');

    break;
    case 2:
    this.$ = Number(yytext);
    break;
    case 3:
    this.$ = null;
    break;
    case 4:
    this.$ = true;
    break;
    case 5:
    this.$ = false;
    break;
    case 6:
    return this.$ = $$[$0-1];
    break;
    case 13:
    this.$ = {}; Object.defineProperty(this.$, '__line__', {
    value: this._$.first_line,
    enumerable: false
    })
    break;
    case 14: case 19:
    this.$ = $$[$0-1]; Object.defineProperty(this.$, '__line__', {
    value: this._$.first_line,
    enumerable: false
    })
    break;
    case 15:
    this.$ = [$$[$0-2], $$[$0]];
    break;
    case 16:
    this.$ = {}; this.$[$$[$0][0]] = $$[$0][1];
    break;
    case 17:

    this.$ = $$[$0-2];
    if ($$[$0-2][$$[$0][0]] !== undefined) {
    if (!this.$.__duplicateProperties__) {
    Object.defineProperty(this.$, '__duplicateProperties__', {
    value: [],
    enumerable: false
    });
    }
    this.$.__duplicateProperties__.push($$[$0][0]);
    }
    $$[$0-2][$$[$0][0]] = $$[$0][1];

    break;
    case 18:
    this.$ = []; Object.defineProperty(this.$, '__line__', {
    value: this._$.first_line,
    enumerable: false
    })
    break;
    case 20:
    this.$ = [$$[$0]];
    break;
    case 21:
    this.$ = $$[$0-2]; $$[$0-2].push($$[$0]);
    break;
    }
    },
    table: [{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,12:1,13:2,15:7,16:8,17:$V5,23:$V6},{1:[3]},{14:[1,16]},o($V7,[2,7]),o($V7,[2,8]),o($V7,[2,9]),o($V7,[2,10]),o($V7,[2,11]),o($V7,[2,12]),o($V7,[2,3]),o($V7,[2,4]),o($V7,[2,5]),o([14,18,21,22,24],[2,1]),o($V7,[2,2]),{3:20,4:$V0,18:[1,17],19:18,20:19},{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:23,15:7,16:8,17:$V5,23:$V6,24:[1,21],25:22},{1:[2,6]},o($V7,[2,13]),{18:[1,24],22:[1,25]},o($V8,[2,16]),{21:[1,26]},o($V7,[2,18]),{22:[1,28],24:[1,27]},o($V9,[2,20]),o($V7,[2,14]),{3:20,4:$V0,20:29},{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:30,15:7,16:8,17:$V5,23:$V6},o($V7,[2,19]),{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:31,15:7,16:8,17:$V5,23:$V6},o($V8,[2,17]),o($V8,[2,15]),o($V9,[2,21])],
    defaultActions: {16:[2,6]},
    parseError: function parseError(str, hash) {
    if (hash.recoverable) {
    this.trace(str);
    } else {
    function _parseError (msg, hash) {
    this.message = msg;
    this.hash = hash;
    }
    _parseError.prototype = Error;

    throw new _parseError(str, hash);
    }
    },
    parse: function parse(input) {
    var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
    var args = lstack.slice.call(arguments, 1);
    var lexer = Object.create(this.lexer);
    var sharedState = { yy: {} };
    for (var k in this.yy) {
    if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
    sharedState.yy[k] = this.yy[k];
    }
    }
    lexer.setInput(input, sharedState.yy);
    sharedState.yy.lexer = lexer;
    sharedState.yy.parser = this;
    if (typeof lexer.yylloc == 'undefined') {
    lexer.yylloc = {};
    }
    var yyloc = lexer.yylloc;
    lstack.push(yyloc);
    var ranges = lexer.options && lexer.options.ranges;
    if (typeof sharedState.yy.parseError === 'function') {
    this.parseError = sharedState.yy.parseError;
    } else {
    this.parseError = Object.getPrototypeOf(this).parseError;
    }
    function popStack(n) {
    stack.length = stack.length - 2 * n;
    vstack.length = vstack.length - n;
    lstack.length = lstack.length - n;
    }
    _token_stack:
    var lex = function () {
    var token;
    token = lexer.lex() || EOF;
    if (typeof token !== 'number') {
    token = self.symbols_[token] || token;
    }
    return token;
    };
    var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
    while (true) {
    state = stack[stack.length - 1];
    if (this.defaultActions[state]) {
    action = this.defaultActions[state];
    } else {
    if (symbol === null || typeof symbol == 'undefined') {
    symbol = lex();
    }
    action = table[state] && table[state][symbol];
    }
    if (typeof action === 'undefined' || !action.length || !action[0]) {
    var errStr = '';
    expected = [];
    for (p in table[state]) {
    if (this.terminals_[p] && p > TERROR) {
    expected.push('\'' + this.terminals_[p] + '\'');
    }
    }
    if (lexer.showPosition) {
    errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
    } else {
    errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
    }
    this.parseError(errStr, {
    text: lexer.match,
    token: this.terminals_[symbol] || symbol,
    line: lexer.yylineno,
    loc: yyloc,
    expected: expected
    });
    }
    if (action[0] instanceof Array && action.length > 1) {
    throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
    }
    switch (action[0]) {
    case 1:
    stack.push(symbol);
    vstack.push(lexer.yytext);
    lstack.push(lexer.yylloc);
    stack.push(action[1]);
    symbol = null;
    if (!preErrorSymbol) {
    yyleng = lexer.yyleng;
    yytext = lexer.yytext;
    yylineno = lexer.yylineno;
    yyloc = lexer.yylloc;
    if (recovering > 0) {
    recovering--;
    }
    } else {
    symbol = preErrorSymbol;
    preErrorSymbol = null;
    }
    break;
    case 2:
    len = this.productions_[action[1]][1];
    yyval.$ = vstack[vstack.length - len];
    yyval._$ = {
    first_line: lstack[lstack.length - (len || 1)].first_line,
    last_line: lstack[lstack.length - 1].last_line,
    first_column: lstack[lstack.length - (len || 1)].first_column,
    last_column: lstack[lstack.length - 1].last_column
    };
    if (ranges) {
    yyval._$.range = [
    lstack[lstack.length - (len || 1)].range[0],
    lstack[lstack.length - 1].range[1]
    ];
    }
    r = this.performAction.apply(yyval, [
    yytext,
    yyleng,
    yylineno,
    sharedState.yy,
    action[1],
    vstack,
    lstack
    ].concat(args));
    if (typeof r !== 'undefined') {
    return r;
    }
    if (len) {
    stack = stack.slice(0, -1 * len * 2);
    vstack = vstack.slice(0, -1 * len);
    lstack = lstack.slice(0, -1 * len);
    }
    stack.push(this.productions_[action[1]][0]);
    vstack.push(yyval.$);
    lstack.push(yyval._$);
    newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
    stack.push(newState);
    break;
    case 3:
    return true;
    }
    }
    return true;
    }};
    /* generated by jison-lex 0.3.4 */
    var lexer = (function(){
    var lexer = ({

    EOF:1,

    parseError:function parseError(str, hash) {
    if (this.yy.parser) {
    this.yy.parser.parseError(str, hash);
    } else {
    throw new Error(str);
    }
    },

    // resets the lexer, sets new input
    setInput:function (input, yy) {
    this.yy = yy || this.yy || {};
    this._input = input;
    this._more = this._backtrack = this.done = false;
    this.yylineno = this.yyleng = 0;
    this.yytext = this.matched = this.match = '';
    this.conditionStack = ['INITIAL'];
    this.yylloc = {
    first_line: 1,
    first_column: 0,
    last_line: 1,
    last_column: 0
    };
    if (this.options.ranges) {
    this.yylloc.range = [0,0];
    }
    this.offset = 0;
    return this;
    },

    // consumes and returns one char from the input
    input:function () {
    var ch = this._input[0];
    this.yytext += ch;
    this.yyleng++;
    this.offset++;
    this.match += ch;
    this.matched += ch;
    var lines = ch.match(/(?:\r\n?|\n).*/g);
    if (lines) {
    this.yylineno++;
    this.yylloc.last_line++;
    } else {
    this.yylloc.last_column++;
    }
    if (this.options.ranges) {
    this.yylloc.range[1]++;
    }

    this._input = this._input.slice(1);
    return ch;
    },

    // unshifts one char (or a string) into the input
    unput:function (ch) {
    var len = ch.length;
    var lines = ch.split(/(?:\r\n?|\n)/g);

    this._input = ch + this._input;
    this.yytext = this.yytext.substr(0, this.yytext.length - len);
    //this.yyleng -= len;
    this.offset -= len;
    var oldLines = this.match.split(/(?:\r\n?|\n)/g);
    this.match = this.match.substr(0, this.match.length - 1);
    this.matched = this.matched.substr(0, this.matched.length - 1);

    if (lines.length - 1) {
    this.yylineno -= lines.length - 1;
    }
    var r = this.yylloc.range;

    this.yylloc = {
    first_line: this.yylloc.first_line,
    last_line: this.yylineno + 1,
    first_column: this.yylloc.first_column,
    last_column: lines ?
    (lines.length === oldLines.length ? this.yylloc.first_column : 0)
    + oldLines[oldLines.length - lines.length].length - lines[0].length :
    this.yylloc.first_column - len
    };

    if (this.options.ranges) {
    this.yylloc.range = [r[0], r[0] + this.yyleng - len];
    }
    this.yyleng = this.yytext.length;
    return this;
    },

    // When called from action, caches matched text and appends it on next action
    more:function () {
    this._more = true;
    return this;
    },

    // When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
    reject:function () {
    if (this.options.backtrack_lexer) {
    this._backtrack = true;
    } else {
    return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
    text: "",
    token: null,
    line: this.yylineno
    });

    }
    return this;
    },

    // retain first n characters of the match
    less:function (n) {
    this.unput(this.match.slice(n));
    },

    // displays already matched input, i.e. for error messages
    pastInput:function () {
    var past = this.matched.substr(0, this.matched.length - this.match.length);
    return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
    },

    // displays upcoming input, i.e. for error messages
    upcomingInput:function () {
    var next = this.match;
    if (next.length < 20) {
    next += this._input.substr(0, 20-next.length);
    }
    return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
    },

    // displays the character position where the lexing error occurred, i.e. for error messages
    showPosition:function () {
    var pre = this.pastInput();
    var c = new Array(pre.length + 1).join("-");
    return pre + this.upcomingInput() + "\n" + c + "^";
    },

    // test the lexed token: return FALSE when not a match, otherwise return token
    test_match:function (match, indexed_rule) {
    var token,
    lines,
    backup;

    if (this.options.backtrack_lexer) {
    // save context
    backup = {
    yylineno: this.yylineno,
    yylloc: {
    first_line: this.yylloc.first_line,
    last_line: this.last_line,
    first_column: this.yylloc.first_column,
    last_column: this.yylloc.last_column
    },
    yytext: this.yytext,
    match: this.match,
    matches: this.matches,
    matched: this.matched,
    yyleng: this.yyleng,
    offset: this.offset,
    _more: this._more,
    _input: this._input,
    yy: this.yy,
    conditionStack: this.conditionStack.slice(0),
    done: this.done
    };
    if (this.options.ranges) {
    backup.yylloc.range = this.yylloc.range.slice(0);
    }
    }

    lines = match[0].match(/(?:\r\n?|\n).*/g);
    if (lines) {
    this.yylineno += lines.length;
    }
    this.yylloc = {
    first_line: this.yylloc.last_line,
    last_line: this.yylineno + 1,
    first_column: this.yylloc.last_column,
    last_column: lines ?
    lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
    this.yylloc.last_column + match[0].length
    };
    this.yytext += match[0];
    this.match += match[0];
    this.matches = match;
    this.yyleng = this.yytext.length;
    if (this.options.ranges) {
    this.yylloc.range = [this.offset, this.offset += this.yyleng];
    }
    this._more = false;
    this._backtrack = false;
    this._input = this._input.slice(match[0].length);
    this.matched += match[0];
    token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
    if (this.done && this._input) {
    this.done = false;
    }
    if (token) {
    return token;
    } else if (this._backtrack) {
    // recover context
    for (var k in backup) {
    this[k] = backup[k];
    }
    return false; // rule action called reject() implying the next rule should be tested instead.
    }
    return false;
    },

    // return next match in input
    next:function () {
    if (this.done) {
    return this.EOF;
    }
    if (!this._input) {
    this.done = true;
    }

    var token,
    match,
    tempMatch,
    index;
    if (!this._more) {
    this.yytext = '';
    this.match = '';
    }
    var rules = this._currentRules();
    for (var i = 0; i < rules.length; i++) {
    tempMatch = this._input.match(this.rules[rules[i]]);
    if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
    match = tempMatch;
    index = i;
    if (this.options.backtrack_lexer) {
    token = this.test_match(tempMatch, rules[i]);
    if (token !== false) {
    return token;
    } else if (this._backtrack) {
    match = false;
    continue; // rule action called reject() implying a rule MISmatch.
    } else {
    // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
    return false;
    }
    } else if (!this.options.flex) {
    break;
    }
    }
    }
    if (match) {
    token = this.test_match(match, rules[index]);
    if (token !== false) {
    return token;
    }
    // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
    return false;
    }
    if (this._input === "") {
    return this.EOF;
    } else {
    return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
    text: "",
    token: null,
    line: this.yylineno
    });
    }
    },

    // return next match that has a token
    lex:function lex() {
    var r = this.next();
    if (r) {
    return r;
    } else {
    return this.lex();
    }
    },

    // activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
    begin:function begin(condition) {
    this.conditionStack.push(condition);
    },

    // pop the previously active lexer condition state off the condition stack
    popState:function popState() {
    var n = this.conditionStack.length - 1;
    if (n > 0) {
    return this.conditionStack.pop();
    } else {
    return this.conditionStack[0];
    }
    },

    // produce the lexer rule set which is active for the currently active lexer condition state
    _currentRules:function _currentRules() {
    if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
    return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
    } else {
    return this.conditions["INITIAL"].rules;
    }
    },

    // return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
    topState:function topState(n) {
    n = this.conditionStack.length - 1 - Math.abs(n || 0);
    if (n >= 0) {
    return this.conditionStack[n];
    } else {
    return "INITIAL";
    }
    },

    // alias for begin(condition)
    pushState:function pushState(condition) {
    this.begin(condition);
    },

    // return the number of states currently on the stack
    stateStackSize:function stateStackSize() {
    return this.conditionStack.length;
    },
    options: {},
    performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
    var YYSTATE=YY_START;
    switch($avoiding_name_collisions) {
    case 0:/* skip whitespace */
    break;
    case 1:return 6
    break;
    case 2:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2); return 4
    break;
    case 3:return 17
    break;
    case 4:return 18
    break;
    case 5:return 23
    break;
    case 6:return 24
    break;
    case 7:return 22
    break;
    case 8:return 21
    break;
    case 9:return 10
    break;
    case 10:return 11
    break;
    case 11:return 8
    break;
    case 12:return 14
    break;
    case 13:return 'INVALID'
    break;
    }
    },
    rules: [/^(?:\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\.[0-9]+)?([eE][-+]?[0-9]+)?\b)/,/^(?:"(?:\\[\\"bfnrt\/]|\\u[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*")/,/^(?:\{)/,/^(?:\})/,/^(?:\[)/,/^(?:\])/,/^(?:,)/,/^(?::)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:null\b)/,/^(?:$)/,/^(?:.)/],
    conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"inclusive":true}}
    });
    return lexer;
    })();
    parser.lexer = lexer;
    function Parser () {
    this.yy = {};
    }
    Parser.prototype = parser;parser.Parser = Parser;
    return new Parser;
    })();


    if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
    exports.parser = jsonlint;
    exports.Parser = jsonlint.Parser;
    exports.parse = function () { return jsonlint.parse.apply(jsonlint, arguments); };
    exports.main = function commonjsMain(args) {
    if (!args[1]) {
    console.log('Usage: '+args[0]+' FILE');
    process.exit(1);
    }
    var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8");
    return exports.parser.parse(source);
    };
    if (typeof module !== 'undefined' && require.main === module) {
    exports.main(process.argv.slice(1));
    }
    }
    }).call(this,require('_process'))
    },{"_process":6,"fs":4,"path":5}],2:[function(require,module,exports){
    /**
    * @alias geojsonhint
    * @param {(string|object)} GeoJSON given as a string or as an object
    * @param {Object} options
    * @param {boolean} [options.noDuplicateMembers=true] forbid repeated
    * properties. This is only available for string input, becaused parsed
    * Objects cannot have duplicate properties.
    * @returns {Array<Object>} an array of errors
    */
    function hint(gj, options) {

    var errors = [];
    var precisionWarningCount = 0;

    function root(_) {

    if ((!options || options.noDuplicateMembers !== false) &&
    _.__duplicateProperties__) {
    errors.push({
    message: 'An object contained duplicate members, making parsing ambigous: ' + _.__duplicateProperties__.join(', '),
    line: _.__line__
    });
    }

    if (!_.type) {
    errors.push({
    message: 'The type property is required and was not found',
    line: _.__line__
    });
    } else if (!types[_.type]) {
    var expectedType = typesLower[_.type.toLowerCase()];
    if (expectedType !== undefined) {
    errors.push({
    message: 'Expected ' + expectedType + ' but got ' + _.type + ' (case sensitive)',
    line: _.__line__
    });
    } else {
    errors.push({
    message: 'The type ' + _.type + ' is unknown',
    line: _.__line__
    });
    }
    } else if (_) {
    types[_.type](_);
    }
    }

    function everyIs(_, type) {
    // make a single exception because typeof null === 'object'
    return _.every(function(x) { return (x !== null) && (typeof x === type); });
    }

    function requiredProperty(_, name, type) {
    if (typeof _[name] === 'undefined') {
    return errors.push({
    message: '"' + name + '" property required',
    line: _.__line__
    });
    } else if (type === 'array') {
    if (!Array.isArray(_[name])) {
    return errors.push({
    message: '"' + name +
    '" property should be an array, but is an ' +
    (typeof _[name]) + ' instead',
    line: _.__line__
    });
    }
    } else if (type && typeof _[name] !== type) {
    return errors.push({
    message: '"' + name +
    '" property should be ' + (type) +
    ', but is an ' + (typeof _[name]) + ' instead',
    line: _.__line__
    });
    }
    }

    // http://geojson.org/geojson-spec.html#feature-collection-objects
    function FeatureCollection(featureCollection) {
    crs(featureCollection);
    bbox(featureCollection);
    if (featureCollection.properties !== undefined) {
    errors.push({
    message: 'FeatureCollection object cannot contain a "properties" member',
    line: featureCollection.__line__
    });
    }
    if (featureCollection.coordinates !== undefined) {
    errors.push({
    message: 'FeatureCollection object cannot contain a "coordinates" member',
    line: featureCollection.__line__
    });
    }
    if (!requiredProperty(featureCollection, 'features', 'array')) {
    if (!everyIs(featureCollection.features, 'object')) {
    return errors.push({
    message: 'Every feature must be an object',
    line: featureCollection.__line__
    });
    }
    featureCollection.features.forEach(Feature);
    }
    }

    // http://geojson.org/geojson-spec.html#positions
    function position(_, line) {
    if (!Array.isArray(_)) {
    return errors.push({
    message: 'position should be an array, is a ' + (typeof _) +
    ' instead',
    line: _.__line__ || line
    });
    } else {
    if (_.length < 2) {
    return errors.push({
    message: 'position must have 2 or more elements',
    line: _.__line__ || line
    });
    }
    if (_.length > 3) {
    return errors.push({
    message: 'position should not have more than 3 elements',
    line: _.__line__ || line
    });
    }
    if (!everyIs(_, 'number')) {
    return errors.push({
    message: 'each element in a position must be a number',
    line: _.__line__ || line
    });
    }

    var maxPrecisionWarnings = 10;
    var maxPrecision = 6;
    var num;
    if (precisionWarningCount == maxPrecisionWarnings) {
    precisionWarningCount += 1;
    return errors.push({
    message: "truncated warnings: we've encountered coordinate precision warning " + maxPrecisionWarnings + " times, no more warnings will be reported",
    level: "warn",
    line: _.__line__ || line
    });
    } else if (precisionWarningCount < maxPrecisionWarnings) {
    _.forEach(function(num) {
    // TODO there has got to be a better way. Check original text?
    // By this point number has already been parsed to a float...
    var precision = 0;
    var decimalStr = (num + "").split(".")[1];
    if (decimalStr !== undefined)
    precision = decimalStr.length;
    if (precision > maxPrecision) {
    precisionWarningCount += 1;
    return errors.push({
    message: "precision of coordinates should be reduced",
    level: "warn",
    line: _.__line__ || line
    });
    }

    });
    }
    }
    }

    function positionArray(coords, type, depth, line) {
    if (line === undefined && coords.__line__ !== undefined) {
    line = coords.__line__;
    }
    if (depth === 0) {
    return position(coords, line);
    } else {
    if (depth === 1 && type) {
    if (type === 'LinearRing') {
    if (!Array.isArray(coords[coords.length - 1])) {
    errors.push({
    message: 'a number was found where a coordinate array should have been found: this needs to be nested more deeply',
    line: line
    });
    return true;
    }
    if (coords.length < 4) {
    errors.push({
    message: 'a LinearRing of coordinates needs to have four or more positions',
    line: line
    });
    }
    if (coords.length &&
    (coords[coords.length - 1].length !== coords[0].length ||
    !coords[coords.length - 1].every(function(position, index) {
    return coords[0][index] === position;
    }))) {
    errors.push({
    message: 'the first and last positions in a LinearRing of coordinates must be the same',
    line: line
    });
    }
    } else if (type === 'Line' && coords.length < 2) {
    return errors.push({
    message: 'a line needs to have two or more coordinates to be valid',
    line: line
    });
    }
    }
    if (!Array.isArray(coords)) {
    errors.push({
    message: 'a number was found where a coordinate array should have been found: this needs to be nested more deeply',
    line: line
    });
    } else {
    coords.forEach(function(c) {
    positionArray(c, type, depth - 1, c.__line__ || line);
    });
    }
    }
    }

    function rightHandRule (geometry) {
    var rhr = true;
    if (geometry.type == 'Polygon') {
    rhr = isPolyRHR(geometry.coordinates);
    } else if (geometry.type == 'MultiPolygon') {
    if (!geometry.coordinates.every(isPolyRHR))
    rhr = false;
    }
    if (!rhr) {
    errors.push({
    message: 'Polygons and MultiPolygons should follow the right-hand rule',
    level: 'warn',
    line: geometry.__line__
    });
    }
    }

    function isPolyRHR (coords) {
    if (coords && coords.length > 0) {
    if (!isRingClockwise(coords[0]))
    return false;
    var interiorCoords = coords.slice(1, coords.length);
    if (interiorCoords.some(isRingClockwise))
    return false;
    }
    return true;
    }

    function isRingClockwise (coords) {
    var area = 0;
    if (coords.length > 2) {
    var p1, p2;
    for (var i = 0; i < coords.length - 1; i++) {
    p1 = coords[i];
    p2 = coords[i + 1];
    area += rad(p2[0] - p1[0]) * (2 + Math.sin(rad(p1[1])) + Math.sin(rad(p2[1])));
    }
    }

    return area >= 0;
    }

    function rad(x) {
    return x * Math.PI / 180;
    }

    function crs(_) {
    if (!_.crs) return;
    var defaultCRSName = "urn:ogc:def:crs:OGC:1.3:CRS84";
    if (typeof _.crs === 'object' && _.crs.properties && _.crs.properties.name === defaultCRSName) {
    errors.push({
    message: "old-style crs member is not recommended, this object is equivalent to the default and should be removed",
    level: "warn",
    line: _.__line__
    });
    } else {
    errors.push({
    message: "old-style crs member is not recommended",
    level: "warn",
    line: _.__line__
    });
    }
    }

    function bbox(_) {
    if (!_.bbox) { return; }
    if (Array.isArray(_.bbox)) {
    if (!everyIs(_.bbox, 'number')) {
    errors.push({
    message: 'each element in a bbox property must be a number',
    line: _.bbox.__line__
    });
    }
    if (!(_.bbox.length == 4 || _.bbox.length == 6)) {
    errors.push({
    message: 'bbox must contain 4 elements (for 2D) or 6 elements (for 3D)',
    line: _.bbox.__line__
    });
    }
    return errors.length;
    } else {
    errors.push({
    message: 'bbox property must be an array of numbers, but is a ' + (typeof _.bbox),
    line: _.__line__
    });
    }
    }

    function geometrySemantics(geom) {
    if (geom.properties !== undefined) {
    errors.push({
    message: 'geometry object cannot contain a "properties" member',
    line: geom.__line__
    });
    }
    if (geom.geometry !== undefined) {
    errors.push({
    message: 'geometry object cannot contain a "geometry" member',
    line: geom.__line__
    });
    }
    if (geom.features !== undefined) {
    errors.push({
    message: 'geometry object cannot contain a "features" member',
    line: geom.__line__
    });
    }
    }

    // http://geojson.org/geojson-spec.html#point
    function Point(point) {
    crs(point);
    bbox(point);
    geometrySemantics(point);
    if (!requiredProperty(point, 'coordinates', 'array')) {
    position(point.coordinates);
    }
    }

    // http://geojson.org/geojson-spec.html#polygon
    function Polygon(polygon) {
    crs(polygon);
    bbox(polygon);
    if (!requiredProperty(polygon, 'coordinates', 'array')) {
    if (!positionArray(polygon.coordinates, 'LinearRing', 2)) {
    rightHandRule(polygon);
    }
    }
    }

    // http://geojson.org/geojson-spec.html#multipolygon
    function MultiPolygon(multiPolygon) {
    crs(multiPolygon);
    bbox(multiPolygon);
    if (!requiredProperty(multiPolygon, 'coordinates', 'array')) {
    if (!positionArray(multiPolygon.coordinates, 'LinearRing', 3)) {
    rightHandRule(multiPolygon);
    }
    }
    }

    // http://geojson.org/geojson-spec.html#linestring
    function LineString(lineString) {
    crs(lineString);
    bbox(lineString);
    if (!requiredProperty(lineString, 'coordinates', 'array')) {
    positionArray(lineString.coordinates, 'Line', 1);
    }
    }

    // http://geojson.org/geojson-spec.html#multilinestring
    function MultiLineString(multiLineString) {
    crs(multiLineString);
    bbox(multiLineString);
    if (!requiredProperty(multiLineString, 'coordinates', 'array')) {
    positionArray(multiLineString.coordinates, 'Line', 2);
    }
    }

    // http://geojson.org/geojson-spec.html#multipoint
    function MultiPoint(multiPoint) {
    crs(multiPoint);
    bbox(multiPoint);
    if (!requiredProperty(multiPoint, 'coordinates', 'array')) {
    positionArray(multiPoint.coordinates, '', 1);
    }
    }

    function GeometryCollection(geometryCollection) {
    crs(geometryCollection);
    bbox(geometryCollection);
    if (!requiredProperty(geometryCollection, 'geometries', 'array')) {
    if (!everyIs(geometryCollection.geometries, 'object')) {
    errors.push({
    message: 'The geometries array in a GeometryCollection must contain only geometry objects',
    line: geometryCollection.__line__
    });
    }
    if (geometryCollection.geometries.length == 1) {
    errors.push({
    message: 'GeometryCollection with a single geometry should be avoided in favor of single part or a single object of multi-part type',
    level: 'warn',
    line: geometryCollection.geometries.__line__
    });
    }
    geometryCollection.geometries.forEach(function(geometry) {
    if (geometry) {
    if (geometry.type === "GeometryCollection") {
    errors.push({
    message: "GeometryCollection should avoid nested geometry collections",
    level: 'warn',
    line: geometryCollection.geometries.__line__
    });
    }
    root(geometry);
    }
    });
    }
    }

    function Feature(feature) {
    crs(feature);
    bbox(feature);
    // https://github.com/geojson/draft-geojson/blob/master/middle.mkd#feature-object
    if (feature.id !== undefined &&
    typeof feature.id !== 'string' &&
    typeof feature.id !== 'number') {
    errors.push({
    message: 'Feature "id" property must have a string or number value',
    line: feature.__line__
    });
    }
    if (feature.features !== undefined) {
    errors.push({
    message: 'Feature object cannot contain a "features" member',
    line: feature.__line__
    });
    }
    if (feature.coordinates !== undefined) {
    errors.push({
    message: 'Feature object cannot contain a "coordinates" member',
    line: feature.__line__
    });
    }
    if (feature.type !== 'Feature') {
    errors.push({
    message: 'GeoJSON features must have a type=feature property',
    line: feature.__line__
    });
    }
    requiredProperty(feature, 'properties', 'object');
    if (!requiredProperty(feature, 'geometry', 'object')) {
    // http://geojson.org/geojson-spec.html#feature-objects
    // tolerate null geometry
    if (feature.geometry) root(feature.geometry);
    }
    }

    var types = {
    Point: Point,
    Feature: Feature,
    MultiPoint: MultiPoint,
    LineString: LineString,
    MultiLineString: MultiLineString,
    FeatureCollection: FeatureCollection,
    GeometryCollection: GeometryCollection,
    Polygon: Polygon,
    MultiPolygon: MultiPolygon
    };

    var typesLower = Object.keys(types).reduce(function(prev, curr) {
    prev[curr.toLowerCase()] = curr;
    return prev;
    }, {});

    if (typeof gj !== 'object' ||
    gj === null ||
    gj === undefined) {
    errors.push({
    message: 'The root of a GeoJSON object must be an object.',
    line: 0
    });
    return errors;
    }

    root(gj);

    errors.forEach(function(err) {
    if (err.hasOwnProperty('line') && err.line === undefined) {
    delete err.line;
    }
    });

    return errors;
    }

    module.exports.hint = hint;

    },{}],3:[function(require,module,exports){
    var jsonlint = require('jsonlint-lines'),
    geojsonHintObject = require('./object');

    /**
    * @alias geojsonhint
    * @param {(string|object)} GeoJSON given as a string or as an object
    * @param {Object} options
    * @param {boolean} [options.noDuplicateMembers=true] forbid repeated
    * properties. This is only available for string input, becaused parsed
    * Objects cannot have duplicate properties.
    * @returns {Array<Object>} an array of errors
    */
    function hint(str, options) {

    var gj, errors = [];

    if (typeof str === 'object') {
    gj = str;
    } else if (typeof str === 'string') {
    try {
    gj = jsonlint.parse(str);
    } catch(e) {
    var match = e.message.match(/line (\d+)/);
    var lineNumber = parseInt(match[1], 10);
    return [{
    line: lineNumber - 1,
    message: e.message,
    error: e
    }];
    }
    } else {
    return [{
    message: 'Expected string or object as input',
    line: 0
    }];
    }

    errors = errors.concat(geojsonHintObject.hint(gj, options));

    return errors;
    }

    module.exports.hint = hint;

    },{"./object":2,"jsonlint-lines":1}],4:[function(require,module,exports){

    },{}],5:[function(require,module,exports){
    (function (process){
    // Copyright Joyent, Inc. and other Node contributors.
    //
    // Permission is hereby granted, free of charge, to any person obtaining a
    // copy of this software and associated documentation files (the
    // "Software"), to deal in the Software without restriction, including
    // without limitation the rights to use, copy, modify, merge, publish,
    // distribute, sublicense, and/or sell copies of the Software, and to permit
    // persons to whom the Software is furnished to do so, subject to the
    // following conditions:
    //
    // The above copyright notice and this permission notice shall be included
    // in all copies or substantial portions of the Software.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
    // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
    // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
    // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
    // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
    // USE OR OTHER DEALINGS IN THE SOFTWARE.

    // resolves . and .. elements in a path array with directory names there
    // must be no slashes, empty elements, or device names (c:\) in the array
    // (so also no leading and trailing slashes - it does not distinguish
    // relative and absolute paths)
    function normalizeArray(parts, allowAboveRoot) {
    // if the path tries to go above the root, `up` ends up > 0
    var up = 0;
    for (var i = parts.length - 1; i >= 0; i--) {
    var last = parts[i];
    if (last === '.') {
    parts.splice(i, 1);
    } else if (last === '..') {
    parts.splice(i, 1);
    up++;
    } else if (up) {
    parts.splice(i, 1);
    up--;
    }
    }

    // if the path is allowed to go above the root, restore leading ..s
    if (allowAboveRoot) {
    for (; up--; up) {
    parts.unshift('..');
    }
    }

    return parts;
    }

    // Split a filename into [root, dir, basename, ext], unix version
    // 'root' is just a slash, or nothing.
    var splitPathRe =
    /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;
    var splitPath = function(filename) {
    return splitPathRe.exec(filename).slice(1);
    };

    // path.resolve([from ...], to)
    // posix version
    exports.resolve = function() {
    var resolvedPath = '',
    resolvedAbsolute = false;

    for (var i = arguments.length - 1; i >= -1 && !resolvedAbsolute; i--) {
    var path = (i >= 0) ? arguments[i] : process.cwd();

    // Skip empty and invalid entries
    if (typeof path !== 'string') {
    throw new TypeError('Arguments to path.resolve must be strings');
    } else if (!path) {
    continue;
    }

    resolvedPath = path + '/' + resolvedPath;
    resolvedAbsolute = path.charAt(0) === '/';
    }

    // At this point the path should be resolved to a full absolute path, but
    // handle relative paths to be safe (might happen when process.cwd() fails)

    // Normalize the path
    resolvedPath = normalizeArray(filter(resolvedPath.split('/'), function(p) {
    return !!p;
    }), !resolvedAbsolute).join('/');

    return ((resolvedAbsolute ? '/' : '') + resolvedPath) || '.';
    };

    // path.normalize(path)
    // posix version
    exports.normalize = function(path) {
    var isAbsolute = exports.isAbsolute(path),
    trailingSlash = substr(path, -1) === '/';

    // Normalize the path
    path = normalizeArray(filter(path.split('/'), function(p) {
    return !!p;
    }), !isAbsolute).join('/');

    if (!path && !isAbsolute) {
    path = '.';
    }
    if (path && trailingSlash) {
    path += '/';
    }

    return (isAbsolute ? '/' : '') + path;
    };

    // posix version
    exports.isAbsolute = function(path) {
    return path.charAt(0) === '/';
    };

    // posix version
    exports.join = function() {
    var paths = Array.prototype.slice.call(arguments, 0);
    return exports.normalize(filter(paths, function(p, index) {
    if (typeof p !== 'string') {
    throw new TypeError('Arguments to path.join must be strings');
    }
    return p;
    }).join('/'));
    };


    // path.relative(from, to)
    // posix version
    exports.relative = function(from, to) {
    from = exports.resolve(from).substr(1);
    to = exports.resolve(to).substr(1);

    function trim(arr) {
    var start = 0;
    for (; start < arr.length; start++) {
    if (arr[start] !== '') break;
    }

    var end = arr.length - 1;
    for (; end >= 0; end--) {
    if (arr[end] !== '') break;
    }

    if (start > end) return [];
    return arr.slice(start, end - start + 1);
    }

    var fromParts = trim(from.split('/'));
    var toParts = trim(to.split('/'));

    var length = Math.min(fromParts.length, toParts.length);
    var samePartsLength = length;
    for (var i = 0; i < length; i++) {
    if (fromParts[i] !== toParts[i]) {
    samePartsLength = i;
    break;
    }
    }

    var outputParts = [];
    for (var i = samePartsLength; i < fromParts.length; i++) {
    outputParts.push('..');
    }

    outputParts = outputParts.concat(toParts.slice(samePartsLength));

    return outputParts.join('/');
    };

    exports.sep = '/';
    exports.delimiter = ':';

    exports.dirname = function(path) {
    var result = splitPath(path),
    root = result[0],
    dir = result[1];

    if (!root && !dir) {
    // No dirname whatsoever
    return '.';
    }

    if (dir) {
    // It has a dirname, strip trailing slash
    dir = dir.substr(0, dir.length - 1);
    }

    return root + dir;
    };


    exports.basename = function(path, ext) {
    var f = splitPath(path)[2];
    // TODO: make this comparison case-insensitive on windows?
    if (ext && f.substr(-1 * ext.length) === ext) {
    f = f.substr(0, f.length - ext.length);
    }
    return f;
    };


    exports.extname = function(path) {
    return splitPath(path)[3];
    };

    function filter (xs, f) {
    if (xs.filter) return xs.filter(f);
    var res = [];
    for (var i = 0; i < xs.length; i++) {
    if (f(xs[i], i, xs)) res.push(xs[i]);
    }
    return res;
    }

    // String.prototype.substr - negative index don't work in IE8
    var substr = 'ab'.substr(-1) === 'b'
    ? function (str, start, len) { return str.substr(start, len) }
    : function (str, start, len) {
    if (start < 0) start = str.length + start;
    return str.substr(start, len);
    }
    ;

    }).call(this,require('_process'))
    },{"_process":6}],6:[function(require,module,exports){
    // shim for using process in browser

    var process = module.exports = {};
    var queue = [];
    var draining = false;
    var currentQueue;
    var queueIndex = -1;

    function cleanUpNextTick() {
    draining = false;
    if (currentQueue.length) {
    queue = currentQueue.concat(queue);
    } else {
    queueIndex = -1;
    }
    if (queue.length) {
    drainQueue();
    }
    }

    function drainQueue() {
    if (draining) {
    return;
    }
    var timeout = setTimeout(cleanUpNextTick);
    draining = true;

    var len = queue.length;
    while(len) {
    currentQueue = queue;
    queue = [];
    while (++queueIndex < len) {
    if (currentQueue) {
    currentQueue[queueIndex].run();
    }
    }
    queueIndex = -1;
    len = queue.length;
    }
    currentQueue = null;
    draining = false;
    clearTimeout(timeout);
    }

    process.nextTick = function (fun) {
    var args = new Array(arguments.length - 1);
    if (arguments.length > 1) {
    for (var i = 1; i < arguments.length; i++) {
    args[i - 1] = arguments[i];
    }
    }
    queue.push(new Item(fun, args));
    if (queue.length === 1 && !draining) {
    setTimeout(drainQueue, 0);
    }
    };

    // v8 likes predictible objects
    function Item(fun, array) {
    this.fun = fun;
    this.array = array;
    }
    Item.prototype.run = function () {
    this.fun.apply(null, this.array);
    };
    process.title = 'browser';
    process.browser = true;
    process.env = {};
    process.argv = [];
    process.version = ''; // empty string to avoid regexp issues
    process.versions = {};

    function noop() {}

    process.on = noop;
    process.addListener = noop;
    process.once = noop;
    process.off = noop;
    process.removeListener = noop;
    process.removeAllListeners = noop;
    process.emit = noop;

    process.binding = function (name) {
    throw new Error('process.binding is not supported');
    };

    process.cwd = function () { return '/' };
    process.chdir = function (dir) {
    throw new Error('process.chdir is not supported');
    };
    process.umask = function() { return 0; };

    },{}]},{},[3])(3)
    });
    239 changes: 239 additions & 0 deletions js/handleReadJson.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,239 @@
    // Importation of helpers
    importScripts(
    "https://npmcdn.com/geojsonhint@latest/geojsonhint.js",
    "turf_bbox.js",
    "helpers.js"
    );

    // Inital message
    postMessage({
    progress: 0,
    message: "start"
    });


    // handle message send from the main thread
    onmessage = function(e) {
    try {

    /**
    * Initialisation : set local helper and variables
    */

    // init variables
    var errorMsg = "";
    var warningMsg = "";
    var dat = e.data;
    var gJson = dat.json;
    var fileName = dat.fileName;

    // set basic timing function
    timerVal = 0;

    // start timer
    var timerStart = function() {
    timerVal = new Date();
    };

    // give intermediate time, reset timer
    var timerLap = function() {
    var lap = new Date() - timerVal;
    timerStart();
    return lap;
    };

    // printable version of timerLaè
    var timerLapString = function() {
    return " " + timerLap() + " ms ";
    };

    // start timer
    timerStart();


    /**
    * validation : geojson validation with geojsonhint
    */

    // Validation. Is that a valid geojson ?
    var messages = geojsonhint.hint(gJson);
    // extract errors
    var errors = messages.filter(function(x){
    return x.level == "error";
    });
    // extract message
    var warnings = messages.filter(function(x){
    return x.level == "message";
    });

    // set a message with summary
    var logMessage = " geojson validation " +
    " n errors=" + errors.length +
    " n warnings =" + warnings.length + " done in" +
    timerLapString();

    console.log(fileName + ":" + logMessage);

    // send message
    postMessage({
    progress: 60,
    message: logMessage
    });

    // validation : warnings
    if (warnings.length > 0) {
    warningMsg = warnings.length + " warning message(s) found. Check the console for more info";
    postMessage({
    progress: 75,
    msssage: warningMsg
    });
    warnings.forEach(function(x) {
    console.log({file:fileName,warnings:x});
    });
    }
    // varlidation: errors
    if (errors.length > 0) {
    errorMsg = errors.length + " errors found. check the console for more info";
    postMessage({
    progress: 100,
    msssage: errorMsg,
    errorMessage: errorMsg
    });

    errors.forEach(function(x) {
    console.log({file:fileName,errors:x});
    });

    return;
    }


    /**
    * Get extent : get extent using a Turf bbox
    */

    var extent = turf.bbox(gJson);

    // Quick extent validation
    if (
    extent[0] > 180 || extent[0] < -180 ||
    extent[1] > 89 || extent[1] < -89 ||
    extent[2] > 180 || extent[2] < -180 ||
    extent[3] > 89 || extent[3] < -89
    ) {
    errorMsg = fileName + " : extent seems to be out of range: " + extent;

    postMessage({
    progress: 100,
    msssage: errorMsg,
    errorMessage: errorMsg
    });

    console.log({
    "errors": errorMsg
    });
    return;
    }

    postMessage({
    progress: 80,
    message: " extent found in " + timerLapString()
    });


    /**
    * Avoid multi type : we don't handle them for now
    */

    // array of types in data
    var geomTypes = gJson.features
    .map(function(x){
    return x.geometry.type;
    })
    .filter(function(v,i,s){
    return s.indexOf(v) === i;
    });


    postMessage({
    progress: 90,
    message: " geom types =" + geomTypes + " found in " + timerLapString()
    });

    // if more than one type, return an error
    if ( geomTypes.length>1) {
    var msg = "Multi geometry not yet implemented";

    postMessage({
    progress: 100,
    msssage: msg,
    errorMessage: fileName + ": " + msg
    });

    console.log({
    "errors": fileName + ": " + msg + ".(" + geomTypes + ")"
    });
    return;
    }


    /**
    * Set default for a new layer
    */

    // Set random id for source and layer
    var id = "mgl_drop_" + randomString(5) + "_" + fileName ;
    // Set random color
    var ran = Math.random();
    var colA = randomHsl(0.6, ran);
    var colB = randomHsl(0.8, ran);

    // Set default type from geojson type
    var typ = typeSwitcher[geomTypes[0]];

    // Set up default style
    var dummyStyle = {
    "circle": {
    "id": id,
    "source": id,
    "type": typ,
    "paint": {
    "circle-color": colA
    }
    },
    "fill": {
    "id": id,
    "source": id,
    "type": typ,
    "paint": {
    "fill-color": colA,
    "fill-outline-color": colB
    }
    },
    "line": {
    "id": id,
    "source": id,
    "type": typ,
    "paint": {
    "line-color": colA,
    "line-width": 10
    }
    }
    };


    postMessage({
    progress: 99,
    message: "Add layer",
    id: id,
    extent: extent,
    layer: dummyStyle[typ]
    });
    }
    catch(err) {
    postMessage({
    progress: 100,
    errorMessage : err
    });
    }
    };
    132 changes: 132 additions & 0 deletions js/helpers.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,132 @@
    /**
    * Generate a random string of the given length
    * @param {integer} n Number of character
    * @return {string} random string
    */
    var randomString = function(n) {
    var result = "";
    if (!n) n = 5;
    var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for (var i = 0; i < n; i++)
    result += characters.charAt(Math.floor(Math.random() * characters.length));
    return result;
    };
    /**
    * Generate a random hsla color string, with fixed saturation and lightness
    * @param {number} opacity opacity from 0 to 1
    * @param {number} random value from 0 to 1
    * @param {number} saturation from 0 to 100
    * @param {number} lightness from 0 to 100
    */
    var randomHsl = function(opacity, random, saturation, lightness) {
    if (!opacity) opacity = 1;
    if (!saturation) saturation = 100;
    if (!lightness) lightness = 50;
    if (!random) random = Math.random();
    res = "hsla(" + (random * 360) +
    ", " + saturation + "% " +
    ", " + lightness + "% " +
    ", " + opacity + ")";
    return res;
    };

    /**
    * Remove multiple layers by prefix
    * @param {object} map Map object
    * @param {string} prefix Prefix to search for in layers, if something found, remove it
    * @return {array} List of removed layer
    */
    var removeLayersByPrefix = function(map, prefix) {
    var result = [];

    if (map) {
    // no method to get all layers ?
    var layers = map.style._layers;
    for (var l in layers) {
    if (l.indexOf(prefix) > -1) {
    map.removeLayer(l);
    result.push(l);
    }
    }
    }

    return result;
    };

    /**
    * Create and manage multiple progression bar
    * @param {boolean} enable Enable the screen
    * @param {string} id Identifier of the given item
    * @param {number} percent Progress bar percentage
    * @param {string} text Optional text
    */
    var progressScreen = function(enable, id, percent, text ) {

    lScreen = document.getElementsByClassName("loading-screen")[0];

    if (!enable) {
    if (lScreen) lScreen.remove();
    return;
    }

    if (!id || !percent || !text) return;

    if (!lScreen && enable) {
    lBody = document.getElementsByTagName("body")[0];
    lScreen = document.createElement("div");
    lScreen.className = "loading-screen";
    lScreenContainer = document.createElement("div");
    lScreenContainer.className = "loading-container";
    lScreen.appendChild(lScreenContainer);
    lBody.appendChild(lScreen);
    }

    lItem = document.getElementById(id);

    if (!lItem) {
    lItem = document.createElement("div");
    lItem.className = "loading-item";
    lItem.setAttribute("id", id);
    pBarIn = document.createElement("div");
    pBarIn.className = "loading-bar-in";
    pBarOut = document.createElement("div");
    pBarOut.className = "loading-bar-out";
    pBarTxt = document.createElement("div");
    pBarTxt.className = "loading-bar-txt";
    pBarOut.appendChild(pBarIn);
    lItem.appendChild(pBarTxt);
    lItem.appendChild(pBarOut);
    lScreenContainer.appendChild(lItem);
    } else {
    pBarIn = lItem.getElementsByClassName("loading-bar-in")[0];
    pBarTxt = lItem.getElementsByClassName("loading-bar-txt")[0];
    }

    if (percent >= 100) {
    lItem = document.getElementById(id);
    if (lItem) lItem.remove();
    } else {
    pBarIn.style.width = percent + "%";
    pBarTxt.innerHTML = text;
    }

    lItems = lScreenContainer.getElementsByClassName("loading-item");

    if (lItems.length === 0) progressScreen(false);

    };



    // geojson type to mapbox gl type
    var typeSwitcher = {
    "Point": "circle",
    "MultiPoint": "line",
    "LineString": "line",
    "MultiLineString": "line",
    "Polygon": "fill",
    "MultiPolygon": "fill",
    "GeometryCollection": "fill"
    };


    432 changes: 432 additions & 0 deletions js/mapbox-gl.js
    432 additions, 0 deletions not shown because the diff is too large. Please use a local Git client to view these changes.
    8 changes: 8 additions & 0 deletions js/turf_bbox.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,8 @@
    (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.turf = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
    var each=require("turf-meta").coordEach;module.exports=function(r){var e=[1/0,1/0,-(1/0),-(1/0)];return each(r,function(r){e[0]>r[0]&&(e[0]=r[0]),e[1]>r[1]&&(e[1]=r[1]),e[2]<r[0]&&(e[2]=r[0]),e[3]<r[1]&&(e[3]=r[1])}),e};
    },{"turf-meta":2}],2:[function(require,module,exports){
    function coordEach(e,o,t){var r,n,c,u,l,p,a,i,f,h,d=0,s="FeatureCollection"===e.type,y="Feature"===e.type,g=s?e.features.length:1;for(r=0;r<g;r++)for(f=s?e.features[r].geometry:y?e.geometry:e,h="GeometryCollection"===f.type,a=h?f.geometries.length:1,u=0;u<a;u++)if(p=h?f.geometries[u]:f,i=p.coordinates,d=!t||"Polygon"!==p.type&&"MultiPolygon"!==p.type?0:1,"Point"===p.type)o(i);else if("LineString"===p.type||"MultiPoint"===p.type)for(n=0;n<i.length;n++)o(i[n]);else if("Polygon"===p.type||"MultiLineString"===p.type)for(n=0;n<i.length;n++)for(c=0;c<i[n].length-d;c++)o(i[n][c]);else{if("MultiPolygon"!==p.type)throw new Error("Unknown Geometry Type");for(n=0;n<i.length;n++)for(c=0;c<i[n].length;c++)for(l=0;l<i[n][c].length-d;l++)o(i[n][c][l])}}function coordReduce(e,o,t,r){return coordEach(e,function(e){t=o(t,e)},r),t}function propEach(e,o){var t;switch(e.type){case"FeatureCollection":for(t=0;t<e.features.length;t++)o(e.features[t].properties);break;case"Feature":o(e.properties)}}function propReduce(e,o,t){return propEach(e,function(e){t=o(t,e)}),t}function featureEach(e,o){if("Feature"===e.type)return o(e);if("FeatureCollection"===e.type)for(var t=0;t<e.features.length;t++)o(e.features[t])}function coordAll(e){var o=[];return coordEach(e,function(e){o.push(e)}),o}module.exports.coordEach=coordEach,module.exports.coordReduce=coordReduce,module.exports.propEach=propEach,module.exports.propReduce=propReduce,module.exports.featureEach=featureEach,module.exports.coordAll=coordAll;
    },{}],3:[function(require,module,exports){
    module.exports={bbox:require("turf-bbox")};
    },{"turf-bbox":1}]},{},[3])(3)
    });
  14. fxi revised this gist Aug 30, 2016. No changes.
  15. fxi revised this gist Aug 29, 2016. 1 changed file with 41 additions and 13 deletions.
    54 changes: 41 additions & 13 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -18,22 +18,37 @@

    <div id='map'></div>
    <script>
    var s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    var randomString = function(n){

    };

    var randomString = function(n){
    /**
    * Generate a random string of the given length
    * @param {integer} n Number of character
    * @return {string} random string
    */
    var randomString = function (n) {
    var result = "";
    if( !n ) n=5;
    var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for( var i=0; i < n; i++ )
    result += characters.charAt(Math.floor(Math.random() * characters.length));
    return result;
    };
    var randomHsl = function (opacity) {
    /**
    * Generate a random hsla color string, with fixed saturation and lightness
    * @param {number} opacity opacity from 0 to 1
    * @param {number} random value from 0 to 1
    * @param {number} saturation from 0 to 100
    * @param {number} lightness from 0 to 100
    */
    var randomHsl = function (opacity, random, saturation, lightness) {
    if (!opacity) opacity = 1;
    return "hsla(" + (Math.random() * 360) + ", 100%, 50%, " + opacity + ")";
    if (!saturation) saturation = 100;
    if (!lightness) lightness = 50;
    if (!random) random = Math.random();
    res = "hsla(" + (random * 360) +
    ", " + saturation + "% " +
    ", " + lightness + "% " +
    ", " + opacity + ")";
    return res;
    };

    // geojson type to mapbox gl type
    @@ -47,7 +62,7 @@
    "GeometryCollection":"fill"
    };


    // mapbox gl init
    mapboxgl.accessToken = 'pk.eyJ1IjoidW5lcGdyaWQiLCJhIjoiY2lzZnowenUwMDAzdjJubzZyZ3R1bjIzZyJ9.uyP-RWjY-94qCVajU0u8KA';
    var map = new mapboxgl.Map({
    container: 'map',
    @@ -56,6 +71,7 @@
    zoom: 10
    });

    // test if file api is available
    if (window.File && window.FileReader && window.FileList && window.Blob) {
    function handleDropGeojson(evt) {
    evt.stopPropagation();
    @@ -85,10 +101,13 @@
    throw(x.message)
    }
    })

    // get extent with geojsonExtent mapbox plugin
    var extent = geojsonExtent(gJson);

    // arra of types in data
    var geomTypes = [] ;

    // Get type for each feature
    gJson.features.forEach(function(x){
    var g = x.geometry.type;
    @@ -102,34 +121,43 @@
    throw(msg);
    }

    // Set random id for source and layer
    var id = "mgl_drop_" + geomTypes[0] + "_" + randomString(5);
    var col = randomHsl(0.6);

    // Set random color
    var ran = Math.random();
    var colA = randomHsl( 0.6, ran );
    var colB = randomHsl( 0.8, ran );

    // Set default type from geojson type
    var typ = typeSwitcher[geomTypes[0]];

    // Set up default style
    var dummyStyle = {
    "circle": {
    "id" : id,
    "source":id,
    "type" : typ,
    "paint" : {
    "circle-color":col
    "circle-color" : colA
    }
    },
    "fill":{
    "id":id,
    "source":id,
    "type":typ,
    "paint":{
    "fill-color":col
    "fill-color":colA,
    "fill-outline-color":colB
    }
    },
    "line":{
    "id":id,
    "source":id,
    "type":typ,
    "paint":{
    "line-color":col,
    "line-width":5
    "line-color":colA,
    "line-width":10
    }
    }
    }
  16. fxi revised this gist Aug 29, 2016. 1 changed file with 12 additions and 12 deletions.
    24 changes: 12 additions & 12 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -24,12 +24,12 @@
    };

    var randomString = function(n){
    var result = "";
    if( !n ) n=5;
    var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for( var i=0; i < n; i++ )
    result += characters.charAt(Math.floor(Math.random() * characters.length));
    return result;
    var result = "";
    if( !n ) n=5;
    var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for( var i=0; i < n; i++ )
    result += characters.charAt(Math.floor(Math.random() * characters.length));
    return result;
    };
    var randomHsl = function (opacity) {
    if (!opacity) opacity = 1;
    @@ -82,7 +82,7 @@
    console.log(x.message)
    }else{
    alert(x.message)
    throw(x.message)
    throw(x.message)
    }
    })
    // get extent with geojsonExtent mapbox plugin
    @@ -96,11 +96,11 @@
    geomTypes.push(g);
    };
    });
    if( geomTypes.length > 1){
    var msg = "Multi geometry not yet implemented";
    alert(msg);
    throw(msg);
    }
    if( geomTypes.length > 1){
    var msg = "Multi geometry not yet implemented";
    alert(msg);
    throw(msg);
    }

    var id = "mgl_drop_" + geomTypes[0] + "_" + randomString(5);
    var col = randomHsl(0.6);
  17. fxi created this gist Aug 29, 2016.
    168 changes: 168 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,168 @@
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset='utf-8' />
    <title></title>
    <meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
    <script src='https://api.tiles.mapbox.com/mapbox-gl-js/v0.23.0/mapbox-gl.js'></script>
    <script src='https://cdn.rawgit.com/mapbox/geojsonhint/master/geojsonhint.js'></script>
    <script src='https://cdn.rawgit.com/mapbox/geojson-extent/master/geojson-extent.js'></script>
    <link href='https://api.tiles.mapbox.com/mapbox-gl-js/v0.23.0/mapbox-gl.css' rel='stylesheet' />

    <style>
    body { margin:0; padding:0; }
    #map { position:absolute; top:0; bottom:0; width:100%; }
    </style>
    </head>
    <body>

    <div id='map'></div>
    <script>
    var s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    var randomString = function(n){

    };

    var randomString = function(n){
    var result = "";
    if( !n ) n=5;
    var characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    for( var i=0; i < n; i++ )
    result += characters.charAt(Math.floor(Math.random() * characters.length));
    return result;
    };
    var randomHsl = function (opacity) {
    if (!opacity) opacity = 1;
    return "hsla(" + (Math.random() * 360) + ", 100%, 50%, " + opacity + ")";
    };

    // geojson type to mapbox gl type
    var typeSwitcher = {
    "Point":"circle",
    "MultiPoint":"line",
    "LineString":"line",
    "MultiLineString":"line",
    "Polygon":"fill",
    "MultiyPolygon":"fill",
    "GeometryCollection":"fill"
    };


    mapboxgl.accessToken = 'pk.eyJ1IjoidW5lcGdyaWQiLCJhIjoiY2lzZnowenUwMDAzdjJubzZyZ3R1bjIzZyJ9.uyP-RWjY-94qCVajU0u8KA';
    var map = new mapboxgl.Map({
    container: 'map',
    style: 'mapbox://styles/mapbox/streets-v9',
    center: [15.6,-4.3],
    zoom: 10
    });

    if (window.File && window.FileReader && window.FileList && window.Blob) {
    function handleDropGeojson(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    var files = evt.dataTransfer.files;

    // In case of multiple file, loop on them
    for (var i = 0, f; f = files[i]; i++) {

    // Only process geojson files. Validate later.
    if(f.name.indexOf(".geojson")==-1){
    continue;
    }

    var reader = new FileReader();

    reader.onload = (function(theFile) {
    return function(e) {
    gJson = JSON.parse(e.target.result);
    // handle simplified geojson validation
    var errors = geojsonhint.hint(gJson);
    errors.forEach(function(x){
    if(x.level=="warn"){
    console.log(x.message)
    }else{
    alert(x.message)
    throw(x.message)
    }
    })
    // get extent with geojsonExtent mapbox plugin
    var extent = geojsonExtent(gJson);
    // arra of types in data
    var geomTypes = [] ;
    // Get type for each feature
    gJson.features.forEach(function(x){
    var g = x.geometry.type;
    if( geomTypes.length == 0 || geomTypes.indexOf(g) == -1 ){
    geomTypes.push(g);
    };
    });
    if( geomTypes.length > 1){
    var msg = "Multi geometry not yet implemented";
    alert(msg);
    throw(msg);
    }

    var id = "mgl_drop_" + geomTypes[0] + "_" + randomString(5);
    var col = randomHsl(0.6);
    var typ = typeSwitcher[geomTypes[0]];

    var dummyStyle = {
    "circle": {
    "id" : id,
    "source":id,
    "type" : typ,
    "paint" : {
    "circle-color":col
    }
    },
    "fill":{
    "id":id,
    "source":id,
    "type":typ,
    "paint":{
    "fill-color":col
    }
    },
    "line":{
    "id":id,
    "source":id,
    "type":typ,
    "paint":{
    "line-color":col,
    "line-width":5
    }
    }
    }

    // Add source and layer;
    map.fitBounds(extent);
    map.addSource(id,{type:'geojson',data:gJson});
    map.addLayer(dummyStyle[typ]);
    };
    })(f);

    reader.readAsText(f);
    }
    }

    function handleDragOver(evt) {
    evt.stopPropagation();
    evt.preventDefault();
    evt.dataTransfer.dropEffect = 'copy'; // Explicitly show this is a copy.
    }

    // Set events
    mapEl = document.getElementById("map");
    mapEl.addEventListener('dragover', handleDragOver, false);
    mapEl.addEventListener('drop', handleDropGeojson, false);


    } else {
    alert('The File APIs are not fully supported in this browser.');
    }


    </script>

    </body>
    </html>