WME Junction Angle info

Show the angle between two selected (and connected) segments

当前为 2015-02-04 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name                WME Junction Angle info
// @namespace           https://github.com/milkboy/WME-ja
// @description         Show the angle between two selected (and connected) segments
// @include             /^https:\/\/(www|editor-beta)\.waze\.com\/(.{2,6}\/)?editor\/.*$/
// @version             1.6.4
// @grant               none
// @copyright           2015 Michael Wikberg <[email protected]>
// @license             CC-BY-NC-SA
// ==/UserScript==

/**
 * Copyright 2014 Michael Wikberg <[email protected]>
 * WME Junction Angle Info extension is licensed under a Creative Commons
 * Attribution-NonCommercial-ShareAlike 3.0 Unported License.
 *
 * Contributions by:
 *     2014 Paweł Pyrczak "tkr85" <[email protected]>
 *     2014 "AlanOfTheBerg" <[email protected]>
 *     2014 "berestovskyy" <?>
 */

function run_ja() {

    var junctionangle_version = "1.6.4";
    var junctionangle_debug = 1;	//0: no output, 1: basic info, 2: debug 3: crazy debug
    var $;
    var ja_features = [];

    var ja_last_restart = 0;

    var ja_routing_type = {
        BC: "junction_none",
        KEEP: "junction_keep",
        TURN: "junction",
        EXIT: "junction_exit", //not actually used (yet)
        PROBLEM: "junction_problem",
        ERROR: "junction_error"
    };
    
    var ja_road_type = {
    	PRIMARY_STREET: 1,
    	STREET: 2,
    	RAMP: 4,
    	H1: 3,
    	H2: 4,
    	H3: 6,
    	H4: 7
    };

    function ja_bootstrap() {
        try {
            if ((typeof window.Waze.map !== 'undefined') && ('undefined' !== typeof window.Waze.map.events.register) &&
                ('undefined' !== typeof window.Waze.selectionManager.events.register ) &&
                ('undefined' !== typeof window.Waze.loginManager.events.register)) {
                setTimeout(function(){junctionangle_init();}, 500);
            } else {
                setTimeout(function(){ja_bootstrap();}, 1000);
            }
        } catch (err) {
            setTimeout(function(){ja_bootstrap();}, 1000);
        }
    }

    function ja_log(ja_log_msg, ja_log_level) {

        if (ja_log_level <= junctionangle_debug) {
            if (typeof ja_log_msg == "object") {
                console.debug(ja_log_msg);
            }
            else {
                console.debug("WME Junction Angle: " + ja_log_msg);
            }
        }
    }

    function ja_get_style_rule(routingType, fillColorOption) {
        return new window.OpenLayers.Rule(
        	{
                filter: new window.OpenLayers.Filter.Comparison({
                    type: window.OpenLayers.Filter.Comparison.EQUAL_TO,
                    property: "ja_type",
                    value: routingType
                }),
                symbolizer: {
                    pointRadius: 3 + parseInt(ja_getOption("pointSize"), 10) + (parseInt(ja_getOption("decimals")) > 0 ? 5 * parseInt(ja_getOption("decimals")) : 0),
                    fontSize: "12px",
                    fillColor: ja_getOption(fillColorOption),
                    strokeColor: "#183800"
                }
            })
    }
    /**
     * Make some style settings
     */
    function ja_style() {
        ja_log("Point radius will be: " + (parseInt(ja_getOption("pointSize"), 10)) + (parseInt(ja_getOption("decimals") > 0 ? 5 * parseInt(ja_getOption("decimals")) : 0)));
        return new window.OpenLayers.Style({
            fillColor: "#ffcc88",
            strokeColor: "#ff9966",
            strokeWidth: 2,
            label: "${angle}",
            fontWeight: "bold",
            pointRadius: parseInt(ja_getOption("pointSize"), 10) + (parseInt(ja_getOption("decimals")) > 0 ? 5 * parseInt(ja_getOption("decimals")) : 0),
            fontSize: "10px"
        }, {
            rules: [
                new window.OpenLayers.Rule({
                    symbolizer: {
                    }
                }),
                ja_get_style_rule(ja_routing_type.TURN, "turnInstructionColor", "#183800"),
                ja_get_style_rule(ja_routing_type.BC, "noInstructionColor", "#183800"),
                ja_get_style_rule(ja_routing_type.KEEP, "keepInstructionColor", "#183800"),
                ja_get_style_rule(ja_routing_type.EXIT, "exitInstructionColor", "#183800"),
                ja_get_style_rule(ja_routing_type.PROBLEM, "problemColor", "#183800"),
                ja_get_style_rule(ja_routing_type.ERROR, "problemColor", "#ff0000")
            ]
        });
    }

    var ja_settings = {
        guess: { elementType: "checkbox", elementId: "_jaCbGuessRouting", defaultValue: false},
        noInstructionColor: { elementType: "color", elementId: "_jaTbNoInstructionColor", defaultValue: "#ffffff"},
        keepInstructionColor: { elementType: "color", elementId: "_jaTbKeepInstructionColor", defaultValue: "#cbff84"},
        exitInstructionColor: { elementType: "color", elementId: "_jaTbExitInstructionColor", defaultValue: "#6cb5ff"},
        turnInstructionColor: { elementType: "color", elementId: "_jaTbTurnInstructionColor", defaultValue: "#4cc600"},
        problemColor: { elementType: "color", elementId: "_jaTbProblemColor", defaultValue: "#a0a0a0"},
        decimals: { elementType: "number", elementId: "_jaTbDecimals", defaultValue: 0, min: 0, max: 2},
        pointSize: { elementType: "number", elementId: "_jaTbPointSize", defaultValue: 12, min: 6, max: 20}
    };

    function junctionangle_init() {

        //Listen for selected nodes change event
        window.Waze.selectionManager.events.register("selectionchanged", null, ja_calculate);

        window.Waze.model.segments.events.on({
            "objectschanged": ja_calculate,
            "objectsremoved": ja_calculate
        });
        window.Waze.model.nodes.events.on({
            "objectschanged": ja_calculate,
            "objectsremoved": ja_calculate
        });


        //HTML changes after login, even though the page is not reloaded. Better do init again.
        window.Waze.loginManager.events.register("afterloginchanged", null, junctionangle_init);

        //Skipping for now, as changes must be saved manually anyway
		//window.addEventListener("beforeunload", ja_save, false);

		ja_load();
		ja_loadTranslations();
        /**
         * Add config setting
         */
        var ja_settings_dom = document.createElement("div");
        var ja_settings_dom_panel = document.createElement("div");
        var ja_settings_dom_content = document.createElement("div");
		ja_settings_dom_panel.className = "side-panel-section";
		ja_settings_dom_content.className = "tab-content";
		var ja_settings_header = document.createElement('h4');
		ja_settings_header.appendChild(document.createTextNode(ja_getMessage("settingsTitle")));
        ja_settings_dom_content.appendChild(ja_settings_header);

        var form = document.createElement('form');
        var section = document.createElement('div');
		section.className = "form-group";
		form.className = "attributes-form side-panel-section";
		//form.addEventListener("submit", function(f) { return function(evt) { alert('FSCK!!!' + f + evt); evt.preventDefault(); return false;}} (form),true);
        section.id = "jaOptions";
        ja_log("---------- Creating settings HTML ----------", 2);
        Object.getOwnPropertyNames(ja_settings).forEach(function (a,b,c) {
            var setting = ja_settings[a];
			var ja_controls_container = document.createElement('div');
			var ja_input = document.createElement('input');
			var ja_label = document.createElement('label');
			ja_controls_container.className = "controls-container";
			ja_input.type = setting['elementType'];
            switch (setting['elementType']) {
                case 'color':
					ja_input.id = setting['elementId'];
					ja_controls_container.appendChild(ja_input);
                    break;
                case 'number':
					ja_input.id = setting['elementId'];
					ja_input.setAttribute("min", setting['min']);
					ja_input.setAttribute("max", setting['max']);
					ja_controls_container.appendChild(ja_input);
                    break;
                case 'text':
					ja_input.id = setting['elementId'];
					ja_input.size = (setting['max'] ? setting['max'] : 8);
					ja_input.maxlength = (setting['max'] ? setting['max'] : 7);
					ja_controls_container.appendChild(ja_input);
                    break;
                case 'checkbox':
					ja_input.id = setting['elementId'];
					ja_controls_container.appendChild(ja_input);
                    break;
            }

			ja_label.setAttribute("for", setting['elementId']);
			ja_label.appendChild(document.createTextNode(ja_getMessage(a)));
			ja_controls_container.appendChild(ja_label);

			section.appendChild(ja_controls_container);
		});
		section.appendChild(document.createElement('br'));
		
		var ja_apply_button = document.createElement('button');
		ja_apply_button.type = "button";
		ja_apply_button.className = "btn btn-default";
		ja_apply_button.addEventListener("click", ja_save, true);
		ja_apply_button.appendChild(document.createTextNode(ja_getMessage("apply")));
		
		var ja_reset_button = document.createElement('button');
		ja_reset_button.type = "button";
		ja_reset_button.className = "btn btn-default";
		ja_reset_button.addEventListener("click", ja_reset, true);
		ja_reset_button.appendChild(document.createTextNode(ja_getMessage("resetToDefault")));
		
		section.appendChild(ja_apply_button);
		section.appendChild(document.createTextNode(" "));
		section.appendChild(ja_reset_button);

		form.appendChild(section);
        ja_settings_dom_content.appendChild(form);

        var userTabs = document.getElementById('user-info');
        var navTabs = userTabs.getElementsByClassName('nav-tabs', userTabs)[0];
        var tabContent = userTabs.getElementsByClassName('tab-content', userTabs)[0];

        ja_settings_dom.id = "sidepanel-ja";
        ja_settings_dom.className = "tab-pane";

		ja_settings_dom_content.style.paddingTop = "0";
		ja_settings_dom_panel.appendChild(ja_settings_dom_content);
		ja_settings_dom.appendChild(ja_settings_dom_panel);
		
		//Add some version info etc
		var ja_info = document.createElement('ul');
		ja_info.className = "additional-attributes list-unstyled -side-panel-section";
		ja_info.style.fontSize = "11px";
		
		var ja_version_elem = document.createElement('li');
		ja_version_elem.appendChild(document.createTextNode(ja_getMessage("name") + ": v" + junctionangle_version));
		ja_info.appendChild(ja_version_elem);

		ja_settings_dom.appendChild(ja_info);

        if(tabContent != null) {
            tabContent.appendChild(ja_settings_dom);
        } else {
            ja_log("Could not append setting to tabContent!?!", 1);
        }

        jatab = document.createElement('li');
        jatab.innerHTML = '<!--suppress HtmlUnknownAnchorTarget --><a href="#sidepanel-ja" data-toggle="tab">JAI</a>';
        if(navTabs != null)
            navTabs.appendChild(jatab);

        //Add support for translations. Default (and fallback) is "en".
        //Note, don't make typos in "acceleratorName", as it has to match the layer name (with whitespace removed
        // to actually work. Took me a while to figure that out...
		I18n.translations[window.I18n.locale].layers.name["junction_angles"] = ja_getMessage("name");

        //try to see if we already have a layer
        if (window.Waze.map.getLayersBy("uniqueName","junction_angles").length == 0) {

            // Create a vector layer and give it your style map.
            ja_mapLayer = new window.OpenLayers.Layer.Vector(ja_getMessage("name"), {
                displayInLayerSwitcher: true,
                uniqueName: "junction_angles",
                shortcutKey: "S+j",
                accelerator: "toggle" + ja_getMessage("name").replace(/\s+/g,''),
                className: "junction-angles",
                styleMap: new window.OpenLayers.StyleMap(ja_style())
            });

            window.Waze.map.addLayer(ja_mapLayer);
            ja_log("version " + junctionangle_version + " loaded.", 0);

            ja_log(window.Waze.map, 3);
            ja_log(window.Waze.model, 3);
            ja_log(window.Waze.loginManager, 3);
            ja_log(window.Waze.selectionManager, 3);
            ja_log(ja_mapLayer, 3);
            ja_log(window.OpenLayers, 3);
        } else {
            ja_log("Oh, nice.. We already had a layer?", 3);
        }

        ja_apply();
		
		//Do a calculation if we have segments selected (permalink etc)
		if(window.Waze.selectionManager.selectedItems.length > 0) {
			ja_calculate();
		}
    }

    function ja_get_streets(segmentId) {
        var primary = window.Waze.model.streets.objects[window.Waze.model.segments.objects[segmentId].attributes.primaryStreetID];
        var secondary = [];
        window.Waze.model.segments.objects[segmentId].attributes.streetIDs.forEach(function asd(element, index, array) {
            secondary.push(window.Waze.model.streets.objects[element]);
        });
        ja_log(primary, 3);
        ja_log(secondary, 3);
        return { primary: primary, secondary: secondary };
    }

    function ja_primary_name_and_type_match(street_in, streets) {
        ja_log("PNT", 2);
        ja_log(street_in, 2);
        return Object.getOwnPropertyNames(streets).some(function (id, index, array) {
            ja_log("PNT Checking element " + index, 2);
            ja_log(streets[id], 2);
            return (streets[id].primary.name == street_in.primary.name
                && streets[id].primary.type == street_in.primary.type);
        });
    }

    function ja_primary_name_match(street_in, streets) {
        ja_log("PN", 2);
        ja_log(street_in, 2);
        ja_log(streets, 2);
        return Object.getOwnPropertyNames(streets).some(function (id, index, array) {
            element = streets[id];
            ja_log("PN Checking element " + index + " of " + array.length, 2);
            ja_log(element, 2);
            return (element.primary.name == street_in.primary.name);
        });
    }

    /**
     * From wiki:
     * A Cross-match is when the primary name of one segment is identical to the alternate name of an adjacent segment. It had the same priory as a Primary name match.
     * In order for a Cross match to work there must be at least one alt name on both involved segments (even though they don't necessarily match each other).
     * It will work even if the are no Primary names on those segments.
     * It will not work if all three segments at a split have a matching Primary name or a matching Alternate name.
     * @param street_in
     * @param streets
     * @returns {boolean}
     */
    //TODO: test!
    function ja_cross_name_match(street_in, streets) {
        ja_log("CN: init", 2);
        ja_log(street_in, 2);
        ja_log(streets, 2);
        return Object.getOwnPropertyNames(streets).some(function (street_n_id, index, array) {
            street_n_element = streets[street_n_id];
            ja_log("CN: Checking element " + index, 2);
            ja_log(street_n_element, 2);
            return (street_in.secondary.some(function (street_in_secondary, index2, array2){
                ja_log("CN2a: checking n.p: " + street_n_element.primary.name + " vs in.s: " + street_in_secondary.name, 2);
                return street_n_element.primary.name == street_in_secondary.name;
            }) || street_n_element.secondary.some(function (street_n_secondary, index2, array2) {
                ja_log("CN2b: checking in.p: " + street_in.primary.name + " vs n.s: " + street_n_secondary.name, 2);
            }));
        });
    }

    //TODO: TEST
    function ja_alt_name_match(street_in, streets) {
        return Object.getOwnPropertyNames(streets).some(function (street_n_id, index, array) {
            var street_n_element = streets[street_n_id];
            ja_log("AN alt name check: Checking element " + index, 2);
            ja_log(street_n_element, 2);

            if(street_in.secondary.length == 0) return false;
            if(street_n_element.secondary.length == 0) return false;

            return street_in.secondary.some(function (street_in_secondary, index2, array2) {
                ja_log("AN2 checking element " + index2, 2);
                ja_log(street_in_secondary, 2);
                return street_n_element.secondary.some(function (street_n_secondary_element, index3,  array3) {
                    ja_log("AN3 Checking in.s: " + street_in_secondary.name + " vs n.s." + index3 + ": " + street_n_secondary_element.name, 2);
                    return street_in_secondary.name == street_n_secondary_element.name;
                });
            });
        });
    }

    /**
     * Check if segment in type matches any other segments
     * @param segment_in
     * @param segments
     * @returns {boolean}
     */
    function ja_segment_type_match(segment_in, segments) {
        ja_log(segment_in, 2);
        ja_log(segments, 2);
        //ja_log(window.Waze.model.segments, 2);

        return Object.getOwnPropertyNames(segments).some(function (segment_n_id, index, array) {
            var segment_n = segments[segment_n_id];
            ja_log("PT Checking element " + index, 2);
            ja_log(segment_n, 2);
            if(segment_n.attributes.id == segment_in.attributes.id) return false;
            ja_log("PT checking sn.rt " + segment_n.attributes.roadType +
                " vs i.pt: " + segment_in.attributes.roadType, 2);
            return (segment_n.attributes.roadType == segment_in.attributes.roadType);
        });
    }

    function ja_has_alt_name(seg) {
        //Single segment?
        if(seg.hasOwnProperty('primary')) {
            return seg.secondary.length > 0;
        } else {
            return Object.getOwnPropertyNames(seg).some(function (s,i,a) {
                return seg[s].secondary.length > 0;
            });
        }
    }

    //segment or segment array
    function ja_all_ramps(seg) {
        //Single segment?
        if(seg.hasOwnProperty('type')) {
            return seg.isRoutable();
        } else {
            return Object.getOwnPropertyNames(seg).some(function (s,i,a) {
                return !seg[s].isRoutable();
            });
        }
    }

    /**
     * get absolute (or turn) angle between 2 inputs.
     * 0,90,true  -> 90     0,90,false -> -90
     * 0,170,true -> 170    0,170,false -> -10
     * @param aIn absolute s_in angle (from node)
     * @param aOut absolute s_out angle (from node)
     * @param absolute return absolute or turn angle?
     * @returns {number}
     */
    function ja_angle_diff(aIn, aOut, absolute) {
        var a = parseFloat(aOut) - parseFloat(aIn);
        if(a > 180) a -= 360;
        if(a < -180) a+= 360;
        if(absolute) {
            return a;
        } else {

            return a > 0 ? a - 180 : a + 180;
        }
    }
    /**
     *
     * @param node Junction node
     * @param s_in_a "In" segment id
     * @param s_out_a "Out" segment id
     * @param angles array of segment absolute angles [0] angle, [1] segment id, 2[?]
     * @returns {string}
     */
    function ja_guess_routing_instruction(node, s_in_a, s_out_a, angles) {
        ja_log("Guessing routing instructions",2);
        ja_log(node, 3);
        ja_log(s_in_a, 3);
        ja_log(s_out_a, 3);
        ja_log(angles, 3);
        var s_in_id = s_in_a;
        var s_out_id = s_out_a;

        for(k=0; k< angles.length; k++) {
            ja_log(angles[k], 3);
            if (angles[k][1] == s_in_a) {
                s_in_a = angles[k];
                break;
            }
        }
        for(k=0; k< angles.length; k++) {
            ja_log(angles[k], 3);
            if(angles[k][1] == s_out_a) {
                s_out_a = angles[k];
                break;
            }
        }

        var s_n = {}, s_in, s_out = {}, street_n = {}, street_in;
        for(k=0; k<node.attributes.segIDs.length; k++) {
            if (node.attributes.segIDs[k] == s_in_id) {
                s_in = node.model.segments.objects[node.attributes.segIDs[k]];
                street_in = ja_get_streets(node.attributes.segIDs[k]);
                //Set empty name for streets if not defined
                if(typeof street_in.primary.name === 'undefined') {
                    street_in.primary['name'] = "";
                }
            } else {
                if(node.attributes.segIDs[k] == s_out_id) {
                    //store for later use
                    s_out[node.attributes.segIDs[k]] = node.model.segments.objects[node.attributes.segIDs[k]];
                    //Set empty name for streets if not defined
                    if(typeof s_out[node.attributes.segIDs[k]].primary === 'undefined') {
                        s_out[node.attributes.segIDs[k]]['primary'] = { name: "" };
                    }
                }
                s_n[node.attributes.segIDs[k]] = node.model.segments.objects[node.attributes.segIDs[k]];
                street_n[node.attributes.segIDs[k]] = ja_get_streets(node.attributes.segIDs[k]);
                if(typeof street_n[node.attributes.segIDs[k]].primary === 'undefined') {
                    street_n[node.attributes.segIDs[k]]['primary'] = { name: ""};
                }
            }
        }



        ja_log(s_in_a, 3);
        ja_log(s_out_a, 3);
        ja_log(s_n, 3);
        ja_log(street_n,3);
        ja_log(s_in,3);
        ja_log(street_in,2);

        var angle = ja_angle_diff(s_in_a[0], (s_out_a[0]), false);
        ja_log("turn angle is: " + angle, 2);
        //No other possible turns
        if(node.attributes.segIDs.length <= 2) {
            ja_log("Only one possible turn", 2);
            return ja_routing_type.BC;
        } //No instruction

        /*
         *
         * Here be dragons!
         *
         */

        if(!ja_is_turn_allowed(s_in, node, s_out[s_out_id])) {
            //Turn is disallowed!
            return ja_routing_type.ERROR;
        }
        //Is it a roundabout?
        if(false) {
            ja_log("Roundabout logic", 2);
            //FIXME
        } else {
            if(Math.abs(angle) <= 44) {
                ja_log("Turn is <= 44", 2);

                /*
                 Need to filter out turns that have no useful meaning for BC. Hope this won't break anything...
                 */
                var tmp_street_out = {};
                tmp_street_out[s_out_id] = street_n[s_out_id];

                ja_log("Original angles and street_n:", 2);
                ja_log(angles, 2);
                ja_log(street_n, 2);
                ja_log(s_n, 2);
                angles = angles.filter(function (a,b,c) {
                    ja_log("Filtering angle: " + ja_angle_diff(s_in_a,a[0],false), 2);
                    if(Math.abs(ja_angle_diff(s_in_a,a[0],false)) <=45
                        && typeof s_n[a[1]] !== 'undefined'
                        && ja_is_turn_allowed(s_in, node, s_n[a[1]])) {
                        return true;
                    } else {
                        if(street_n[a[1]]) {
                            delete s_n[a[1]];
                            delete street_n[a[1]];
                        }
                        return false;
                    }
                });
                ja_log("Filtered angles and street_n:", 2);
                ja_log(angles, 2);
                ja_log(street_n, 2);
                ja_log(s_n, 2);

                if(angles.length == 1) return ja_routing_type.BC;
                //FIXME: Need to have logic for multiple <45 matches?...
                //Check for other unrestricted <45 turns?
                for(k=0; k< angles.length; k++) {
                    ja_log("Checking angle " + k, 2);
                    ja_log(angles[k],2);

                    ja_log("in: " + s_in_a[0] + ", " + (s_in_a[0] + 180), 3);
                    ja_log("a_n: " + angles[k][0], 3);
                    var tmp_angle = ja_angle_diff(s_in_a[0], angles[k][0], false);
                    ja_log(tmp_angle, 2);
                    
                    //tmp test
                    ja_log("Node getDirectionBetweenSegments", 2);
                    ja_log(node.getAngleToSegment(s_in, s_out[s_out_id]), 2);
                    ja_log(node.allConnectionKeys(s_out[s_out_id]), 2);
                    //end
                    
                    if(
                        Math.abs(tmp_angle < 45) &&  //Angle is < 45
                        ja_is_turn_allowed(s_in, node, s_n[angles[k][1]]) && //Direction is allowed FIXME: Need to check for disallowed turns somehow!
                        Math.abs(ja_angle_diff(angles[k][0],s_out_a[0], true)) > 1 //Arbitrarily chosen angle for "overlapping" segments.
                        ){
                        ja_log("Found other allowed turn <= 44", 2);

                        /*
                         * Begin "best continuation" logic
                         */
                        ja_log("BC 2", 1);
                        //2 Is there any alt on both s-in & any s-n?
                        if(ja_has_alt_name(street_in) && ja_has_alt_name(street_n)) {
                            //3 Is s-out a type match?
                            ja_log("BC 3", 2);
                            //Road types match?
                            if(ja_segment_type_match(s_in, s_out)) {
                                //4 Does s-in have a primary name?
                                ja_log("BC 4", 2);
                                if(street_in.primary.name) {
                                    //5 Is s-out a primary OR cross name match?
                                    ja_log("BC 5", 2);
                                    if(ja_primary_name_match(street_in, tmp_street_out) ||
                                        ja_cross_name_match(street_in,  tmp_street_out)) {
                                        //6 Is any SN a primary name AND type match?
                                        //FIXME: Does this mean match to s_in?
                                        ja_log("BC 6", 2);
                                        if(ja_primary_name_and_type_match(street_in, street_n)) {
                                            ja_log("Found a name+type match", 2);
                                            return ja_routing_type.KEEP;
                                        } else {
                                            return ja_routing_type.BC;
                                        }
                                    } else {
                                        //10    Is any SN a primary name AND type match?
                                        ja_log("BC 10", 2);
                                        if(ja_primary_name_and_type_match(street_in, street_n)) {
                                            return ja_routing_type.KEEP;
                                        } else {
                                            //11    Is s-out an alternate name match?
                                            ja_log("BC 11", 2);
                                            if(!ja_alt_name_match(street_in, tmp_street_out)) {
                                                //12    Is any SN a primary OR cross OR alternate name match?
                                                ja_log("BC 12", 2);
                                                if(ja_primary_name_match(street_in, street_n)
                                                    || ja_cross_name_match(street_in, street_n)
                                                    || ja_alt_name_match(street_in, street_n)) {
                                                    return ja_routing_type.KEEP;
                                                } else {
                                                    return ja_routing_type.BC;
                                                }
                                            } else {
                                                //13    Is any SN an alternate name AND type match?
                                                ja_log("BC 13", 2);
                                                if(ja_alt_name_match(street_in, street_n)
                                                    && ja_segment_type_match(s_in, s_out)) {
                                                    return ja_routing_type.KEEP;
                                                } else {
                                                    return ja_routing_type.BC;
                                                }
                                            }
                                        }
                                    }
                                } else {
                                    //7 Is any SN a primary OR cross match name?
                                    ja_log("BC 7", 2);
                                    if(ja_primary_name_match(street_in, street_n)
                                        || ja_cross_name_match(street_in, street_n)) {
                                        return ja_routing_type.KEEP;
                                    } else {
                                        //8 Is s-out a primary OR cross name match?
                                        ja_log("BC 8", 2);
                                        if(ja_primary_name_match(street_in, tmp_street_out)
                                            || ja_cross_name_match(street_in, tmp_street_out)) {
                                            return ja_routing_type.BC;
                                        } else {
                                            //9 Is any SN a type match?
                                            ja_log("BC 9", 2);
                                            if(ja_segment_type_match(s_in, s_n)) {
                                                return ja_routing_type.KEEP;
                                            } else {
                                                return ja_routing_type.BC;
                                            }
                                        }
                                    }
                                }
                            } else {
                                //14 Is any SN a type match?
                                ja_log("BC 14", 2);
                                if(ja_segment_type_match(s_in, s_n)) {
                                    //15    Is any SN a primary OR cross name match?
                                    ja_log("BC 15", 2);
                                    if(ja_cross_name_match(street_in, street_n || ja_cross_name_match(street_in, street_n))) {
                                        //Keep
                                        return ja_routing_type.KEEP;
                                    } else {
                                        //16    Does s-in have a primary name?
                                        ja_log("BC 16", 2);
                                        if(street_in.primary.name) {
                                            //17    Is s-out a primary OR cross match?
                                            ja_log("BC 17", 2);
                                            if(ja_primary_name_match(street_in, tmp_street_out)
                                                || ja_cross_name_match(street_in, tmp_street_out)) {
                                                return ja_routing_type.BC;
                                            } else {
                                                //18    Is s-out an alternate name match?
                                                ja_log("BC 18", 2);
                                                if(ja_alt_name_match(street_in, tmp_street_out)) {
                                                    //19    Is any SN an alternate name match?
                                                    if(ja_alt_name_match(street_in, street_n)) {
                                                        return ja_routing_type.KEEP;
                                                    } else {
                                                        return ja_routing_type.BC;
                                                    }
                                                } else {
                                                    return ja_routing_type.KEEP;
                                                }
                                            }
                                        } else {
                                            //keep
                                            return ja_routing_type.KEEP;
                                        }
                                    }
                                } else {
                                    //20    Is s-out a primary name match?
                                    ja_log("BC 20", 2);
                                    if(ja_primary_name_match(street_in, tmp_street_out)) {
                                        //21    Is any SN a primary or cross name match?
                                        ja_log("BC 21", 2);
                                        if(ja_primary_name_match(street_in, street_n) || ja_cross_name_match(street_in, street_n)) {
                                            return ja_routing_type.KEEP;
                                        } else {
                                            return ja_routing_type.BC;
                                        }
                                    } else {
                                        //22    Is any SN a primary name match?
                                        ja_log("BC 22", 2);
                                        if(ja_primary_name_match(street_in, street_n)) {
                                            return ja_routing_type.KEEP;
                                        } else {
                                            //23    Is s-out a cross name match?
                                            ja_log("BC 23", 2);
                                            if(ja_cross_name_match(street_in, tmp_street_out)) {
                                                //24    Is any SN a cross name match?
                                                ja_log("BC 24", 2);
                                                if(ja_cross_name_match(street_in, street_n)) {
                                                    return ja_routing_type.KEEP;
                                                } else {
                                                    return ja_routing_type.BC;
                                                }
                                            } else {
                                                //25    Is any SN a cross name match?
                                                ja_log("BC 25", 2);
                                                if(ja_cross_name_match(street_in, street_n)) {
                                                    return ja_routing_type.KEEP;
                                                } else {
                                                    //26    Does s-in have a primary name?
                                                    ja_log("BC 26", 2);
                                                    if(street_in.primary.name) {
                                                        //27    Is s-out an alternate name match?
                                                        ja_log("BC 27", 2);
                                                        if(ja_alt_name_match(street_in, tmp_street_out)) {
                                                            //28    Is any SN an alternate name match?
                                                            ja_log("BC 28", 2);
                                                            if(ja_alt_name_match(street_in, street_n)) {
                                                                return ja_routing_type.KEEP;
                                                            } else {
                                                                return ja_routing_type.BC;
                                                            }
                                                        } else {
                                                            return ja_routing_type.KEEP;
                                                        }
                                                    } else {
                                                        return ja_routing_type.KEEP;
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else {
                            ja_log(".............................", 2);
                            ja_log(s_in,2);
                            ja_log(s_in.isHighway(), 2);
                            ja_log(s_out[s_out_id].isHighway(), 2);
                            ja_log(s_out[s_out_id].isRoutable(), 2);
                            ja_log(s_in.model.isLeftHand, 2);
                            //Highway ends, 2(+?) ramps continuing
                            if(s_in.isHighway() && ja_all_ramps(s_out)) {
                                return ja_routing_type.KEEP;
                            }
                            //Continue straight on highway
                            if(s_in.isHighway() && s_out[s_out_id].isHighway() && !s_n[angles[k][1]].isRoutable()) {
                            	return ja_routing_type.BC;
                            }
                            //Highway -> ramp
                            if(s_in.isHighway() && !s_out[s_out_id].isRoutable()) {
                                //Exit right on RHD, left on LHD
                                if(s_in.model.isLeftHand ? (angle > 0 ) : (angle < 0)) {
                                    return ja_routing_type.EXIT;
                                }
                            }
                            return ja_routing_type.KEEP;
                        }
                    }
                }
                ja_log("\"straight\": no instruction", 2);
                return ja_routing_type.BC;
            } else if(Math.abs(angle) <= 46) {
                ja_log("Angle is in gray zone 44-46", 2);
                return ja_routing_type.PROBLEM;
            } else {
                ja_log("Normal turn", 2);
                return ja_routing_type.TURN; //Normal turn (left|right)
            }
        }
        ja_log("No matching turn instruction logic", 2);
        return ja_routing_type.TURN; //default
    }

    function ja_is_turn_allowed(s_from, via_node, s_to) {
        ja_log("Allow from " + s_from.attributes.id + " to " + s_to.attributes.id + " via " + via_node.attributes.id + "?"
            + via_node.isTurnAllowedBySegDirections(s_from, s_to), 2);

        return via_node.isTurnAllowedBySegDirections(s_from, s_to);
    }

    function ja_calculate() {
        ja_log(window.Waze.map, 3);
        if(typeof ja_mapLayer === 'undefined') { return 1;}
        //clear old info
        ja_mapLayer.destroyFeatures();

        //try to show all angles for all selected segments
        if (window.Waze.selectionManager.selectedItems.length == 0) return 1;
        ja_log("Checking junctions for " + window.Waze.selectionManager.selectedItems.length + " segments", 2);
        var ja_nodes = [];

        for (i = 0; i < window.Waze.selectionManager.selectedItems.length; i++) {
            ja_log(window.Waze.selectionManager.selectedItems[i], 3);
            switch (window.Waze.selectionManager.selectedItems[i].model.type) {
                case "node":
                    ja_nodes.push(window.Waze.selectionManager.selectedItems[i].model.attributes.id);
                    break;
                case "segment":
                    //segments selected?
                    if (window.Waze.selectionManager.selectedItems[i].model.attributes.fromNodeID != null &&
                        ja_nodes.indexOf(window.Waze.selectionManager.selectedItems[i].model.attributes.fromNodeID) == -1) {
                        ja_nodes.push(window.Waze.selectionManager.selectedItems[i].model.attributes.fromNodeID);
                    }
                    if (ja_nodes.indexOf(window.Waze.selectionManager.selectedItems[i].model.attributes.toNodeID != null &&
                        ja_nodes.indexOf(window.Waze.selectionManager.selectedItems[i].model.attributes.toNodeID) == -1)) {
                        ja_nodes.push(window.Waze.selectionManager.selectedItems[i].model.attributes.toNodeID);
                    }
                    break;
                case "venue":
                    break;
                default:
                    ja_log("Found unknown item type: " + window.Waze.selectionManager.selectedItems[i].model.type, 2);
                    break;
            }
            ja_log(ja_nodes, 2);
        }

        ja_features = [];

        for (i = 0; i < ja_nodes.length; i++) {
            node = window.Waze.model.nodes.get(ja_nodes[i]);
            if (node == null || !node.hasOwnProperty('attributes')) {
                //Oh oh.. should not happen? We want to use a node that does not exist
                ja_log("Oh oh.. should not happen?",2);
                ja_log(node, 2);
                ja_log(ja_nodes[i], 2);
                //ja_log(ja_nodes, 2);
                ja_log(window.Waze.model, 3);
                ja_log(window.Waze.model.nodes, 3);
                continue;
            }
            //check connected segments
            var ja_current_node_segments = node.attributes.segIDs;
            ja_log(node, 2);

            //ignore of we have less than 2 segments
            if (ja_current_node_segments.length <= 1) {
                ja_log("Found only " + ja_current_node_segments.length + " connected segments at " + ja_nodes[i] + ", not calculating anything...", 2);
                continue;
            }

            ja_log("Calculating angles for " + ja_current_node_segments.length + " segments", 2);

            var angles = [];
            var ja_selected_segments_count = 0;
            var ja_selected_angles = [];

            for (j = 0; j < ja_current_node_segments.length; j++) {
                s = window.Waze.model.segments.objects[ja_current_node_segments[j]];
                if(typeof s === 'undefined') {
                    //Meh. Something went wrong, and we lost track of the segment. This needs a proper fix, but for now
                    // it should be sufficient to just restart the calculation
                    ja_log("Failed to read segment data from model. Restarting calculations.", 1);
                    if(ja_last_restart == 0) {
                        ja_last_restart = new Date().getTime();
                        setTimeout(function(){ja_calculate();}, 500);
                    }
                    return 4;
                }
                a = ja_getAngle(ja_nodes[i], s);
                ja_log("j: " + j + "; Segment " + ja_current_node_segments[j] + " angle is " + a, 2);
                angles[j] = [a, ja_current_node_segments[j], s != null ? s.isSelected() : false];
                if (s != null ? s.isSelected() : false) {
                    ja_selected_segments_count++;
                }

            }

            //make sure we have the selected angles in correct order
            ja_log(ja_current_node_segments, 3);
            window.Waze.selectionManager.selectedItems.forEach(function (selectedSegment, selectedIndex, selectedItems) {
                var selectedSegmentId = selectedSegment.model.attributes.id;
                ja_log("Checking if " + selectedSegmentId + " is in current node", 3);
                if(ja_current_node_segments.indexOf(selectedSegmentId) >= 0) {
                    ja_log("It is!", 3);
                    //find the angle
                    for(j=0; j < angles.length; j++) {
                        if(angles[j][1] == selectedSegmentId) {
                            ja_selected_angles.push(angles[j]);
                            break;
                        }
                    }
                } else {
                    ja_log("It's not..", 3);
                }
            });


            ja_log(angles, 3);

            var ja_label_distance;
            switch (window.Waze.map.zoom) {
                case 9:
                    ja_label_distance = 4;
                    break;
                case 8:
                    ja_label_distance = 8;
                    break;
                case 7:
                    ja_label_distance = 15;
                    break;
                case 6:
                    ja_label_distance = 25;
                    break;
                case 5:
                    ja_label_distance = 40;
                    break;
                case 4:
                    ja_label_distance = 80;
                    break;
                case 3:
                    ja_label_distance = 140;
                    break;
                case 2:
                    ja_label_distance = 300;
                    break;
                case 1:
                    ja_label_distance = 400;
                    break;
            }

            ja_label_distance = ja_label_distance * (1+(ja_getOption("decimals") > 0 ? 0.2*ja_getOption("decimals") : 0));

            ja_log("zoom: " + window.Waze.map.zoom + " -> distance: " + ja_label_distance, 2);

            var a, ha;
            //if we have two connected segments selected, do some magic to get the turn angle only =)
            if (ja_selected_segments_count == 2) {
                ja_extra_space_multiplier = 1;

                a = ja_angle_diff(ja_selected_angles[0][0], ja_selected_angles[1][0], false);

                ha = (parseFloat(ja_selected_angles[0][0]) + parseFloat(ja_selected_angles[1][0]))/2;
                if(
                    (Math.abs(ja_selected_angles[0][0]) + Math.abs(ja_selected_angles[1][0])) > 180
                && (
                        (ja_selected_angles[0][0] < 0 && ja_selected_angles[1][0] > 0)
                        || (ja_selected_angles[0][0] > 0 && ja_selected_angles[1][0] < 0))
                    ) ha += 180;

                if (Math.abs(a) > 120) {
                    ja_log("Sharp angle", 2);
                    ja_extra_space_multiplier = 2;
                }

                //Move point a bit if it's on the top (Bridge icon will obscure it otherwise)
                if(ha > 40 && ha < 120) ja_extra_space_multiplier = 2;


                ja_log("Angle between " + ja_selected_angles[0][1] + " and " + ja_selected_angles[1][1] + " is " + a + " and position for label should be at " + ha, 2);

                //Guess some routing instructions based on segment types, angles etc
                var ja_junction_type = ja_routing_type.TURN; //Default to old behavior
                if(ja_getOption("guess")) {
                    ja_log(ja_selected_angles, 2);
                    ja_log(angles, 2);
                    ja_junction_type = ja_guess_routing_instruction(node, ja_selected_angles[0][1], ja_selected_angles[1][1], angles);
                    ja_log("Type is: " + ja_junction_type, 2);
                }
                //put the angle point
                ja_features.push(new window.OpenLayers.Feature.Vector(
                    new window.OpenLayers.Geometry.Point(
                        node.geometry.x + (ja_extra_space_multiplier * ja_label_distance * Math.cos((ha * Math.PI) / 180)),
                        node.geometry.y + (ja_extra_space_multiplier * ja_label_distance * Math.sin((ha * Math.PI) / 180))
                    )
                    , { angle: (a>0?"<":"") + ja_round(Math.abs(a)) + "°" + (a<0?">":""), ja_type: ja_junction_type }
                ));
            }
            else {
                //sort angle data (ascending)
                angles.sort(function (a, b) {
                    return a[0] - b[0]
                });
                ja_log(angles, 3);
                ja_log(ja_selected_segments_count, 3);

                //get all segment angles
                for (j = 0; j < angles.length; j++) {
                    a = (360 + (angles[(j + 1) % angles.length][0] - angles[j][0])) % 360;
                    ha = (360 + ((a / 2) + angles[j][0])) % 360;

                    //Show only one angle for nodes with only 2 connected segments and a single selected segment
                    // (not on both sides). Skipping the one > 180
                    if (ja_selected_segments_count == 1
                        && angles.length == 2
                        && (Math.abs(a) > 180
                            || (Math.abs(a)%180 == 0 && j == 0 )
                            )
                        ) {
                        ja_log("Skipping marker, as we need only one of them", 2);
                    } else {
                        ja_log("Angle between " + angles[j][1] + " and " + angles[(j + 1) % angles.length][1] + " is " + a + " and position for label should be at " + ha, 3);
                        //push the angle point
                        ja_features.push(new window.OpenLayers.Feature.Vector(
                            new window.OpenLayers.Geometry.Point(
                                    node.geometry.x + (ja_label_distance * Math.cos((ha * Math.PI) / 180)), node.geometry.y + (ja_label_distance * Math.sin((ha * Math.PI) / 180))
                            )
                            , { angle: ja_round(a) + "°", ja_type: "generic" }
                        ));
                    }
                }
            }
        }

        ja_log(ja_features, 2);
        //Update the displayed angles
        ja_mapLayer.addFeatures(ja_features);
        ja_last_restart = 0;
    }

    function ja_points_equal(point1, point2) {
        return (point1.x == point2.x && point1.y == point2.y);
    }

    function ja_get_first_point(segment) {
        return segment.geometry.components[0];
    }

    function ja_get_last_point(segment) {
        return segment.geometry.components[segment.geometry.components.length - 1];
    }

    function ja_get_second_point(segment) {
        return segment.geometry.components[1];
    }

    function ja_get_next_to_last_point(segment) {
        return segment.geometry.components[segment.geometry.components.length - 2];
    }

    //get the absolute angle for a segment end point
    function ja_getAngle(ja_node, ja_segment) {
        ja_log("node: " + ja_node, 2);
        ja_log("segment: " + ja_segment, 2);
        if (ja_node == null || ja_segment == null) return null;
        if (ja_segment.attributes.fromNodeID == ja_node) {
            ja_dx = ja_get_second_point(ja_segment).x - ja_get_first_point(ja_segment).x;
            ja_dy = ja_get_second_point(ja_segment).y - ja_get_first_point(ja_segment).y;
        } else {
            ja_dx = ja_get_next_to_last_point(ja_segment).x - ja_get_last_point(ja_segment).x;
            ja_dy = ja_get_next_to_last_point(ja_segment).y - ja_get_last_point(ja_segment).y;
        }
        ja_log(ja_node + " / " + ja_segment + ": dx:" + ja_dx + ", dy:" + ja_dy, 2);
        ja_angle = Math.atan2(ja_dy, ja_dx);
        return ((ja_angle * 180 / Math.PI)) % 360;
    }
	
    /**
     * Decimal adjustment of a number. Borrowed (with some modifications) from
     * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round
     * ja_round(55.55, -1); // 55.6
     * ja_round(55.549, -1); // 55.5
     * ja_round(55, 1); // 60
     * ja_round(54.9, 1); // 50
     *
     * @param	{String}	type	The type of adjustment.
     * @param	{Number}	value	The number.
     * @param	{Integer}	exp		The exponent (the 10 logarithm of the adjustment base).
     * @returns	{Number}			The adjusted value.
     */
    function ja_round(value) {
        // If the exp is undefined or zero...
		var ja_rounding = -parseInt(ja_getOption("decimals"));
        if (typeof ja_rounding === 'undefined' || +ja_rounding === 0) {
            return Math.round(value);
        }
        value = +value;
        // If the value is not a number or the exp is not an integer...
        if (isNaN(value) || !(typeof ja_rounding === 'number' && ja_rounding % 1 === 0)) {
            return NaN;
        }
        // Shift
        value = value.toString().split('e');
        value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] - ja_rounding) : -ja_rounding)));
        // Shift back
        value = value.toString().split('e');
        return +(value[0] + 'e' + (value[1] ? (+value[1] + ja_rounding) : ja_rounding));
    }

    var ja_options = {};

    function ja_getOption(name) {
        ja_log("Loading option: " + name, 2);
        if(!ja_options.hasOwnProperty(name) || typeof ja_options[name] === 'undefined') {
            ja_options[name] = ja_settings[name]['defaultValue'];
        }
		
        ja_log("Got value: " + ja_options[name], 2);
        return ja_options[name];
    }

    function ja_setOption(name, val) {
        ja_options[name] = val;
        if(localStorage) {
            localStorage.setItem("wme_ja_options", JSON.stringify(ja_options));
        }
        ja_log(ja_options,3);
    }

    var ja_load = function loadJAOptions() {
        ja_log("Should load settings now.", 2);
        if(localStorage != null) {
            ja_log("We have local storage! =)",2);
            try {
                ja_options = JSON.parse(localStorage.getItem("wme_ja_options"));
            } catch (e){
                ja_log("Loading settings failed.. " + e.message, 2);
                ja_options = null;
            }
        }
        if(ja_options == null) {
            ja_reset();
        } else {
            ja_log(ja_options, 2);
            setTimeout(function(){ja_apply();}, 500);
        }
    };

    ja_save = function saveJAOptions() {
        ja_log("Saving settings", 2);
        Object.getOwnPropertyNames(ja_settings).forEach(function (a,b,c) {
            var setting = ja_settings[a];
            ja_log(setting, 2);
            switch (setting['elementType']) {
                case "checkbox":
                    ja_setOption(a, document.getElementById(setting['elementId']).checked);
                    break;
                case "color":
					var re = /^#[0-9a-f]{6}$/;
					if(re.test(document.getElementById(setting['elementId']).value)) {
						ja_setOption(a, document.getElementById(setting['elementId']).value);
					} else {
						ja_setOption(a, ja_settings[a]['default']);
					}
					break;
                case "number":
					var val = document.getElementById(setting['elementId']).value;
					if(!isNaN(val) && val == parseInt(val) && val >= setting['min'] && val <= setting['max']) {
						ja_setOption(a, document.getElementById(setting['elementId']).value);
					} else {
						ja_setOption(a, ja_settings[a]['default']);
					}
					break;
                case "text":
					ja_setOption(a, document.getElementById(setting['elementId']).value);
                    break;
            }
        });
        ja_apply();
        return false;
    };

    var ja_apply = function applyJAOptions() {
        ja_log("Applying stored (or default) settings", 2);
        if(typeof window.Waze.map.getLayersBy("uniqueName","junction_angles")[0] === 'undefined') {
            ja_log("WME not ready yet, trying again in 400 ms", 2);
            setTimeout(function(){ja_apply();}, 400);
            return;
        }
        if(document.getElementById("sidepanel-ja") != null) {
            ja_log(Object.getOwnPropertyNames(ja_settings), 2);
            Object.getOwnPropertyNames(ja_settings).forEach(function (a,b,c) {
                var setting = ja_settings[a];
                ja_log(a, 2);
                ja_log(setting, 2);
                ja_log(document.getElementById(setting['elementId']), 2);
                switch (setting['elementType']) {
                    case "checkbox":
                        document.getElementById(setting['elementId']).checked = ja_getOption(a);
                        break;
                    case "color":
                    case "number":
                    case "text":
                        document.getElementById(setting['elementId']).value = ja_getOption(a);
                        break;
                }
            });
        } else {
            ja_log("WME not ready (no settings tab)", 2);
        }
        window.Waze.map.getLayersBy("uniqueName","junction_angles")[0].styleMap = ja_style();

        ja_log(ja_options, 2);
    };

    ja_reset = function resetJAOptions() {
        ja_log("Resetting settings", 2);
        if(localStorage != null) {
            localStorage.removeItem("wme_ja_options");
        }
        ja_options = {};
        ja_apply();
		return false;
    };
	
	function ja_getMessage(key) {
		return I18n.translate('ja.' + key);
	}
	
	function ja_loadTranslations() {
		ja_log("Loading translations",2);
		I18n.translations[window.I18n.defaultLocale].ja = {};
		def = I18n.translations[window.I18n.defaultLocale].ja;
		sv = {};
		fi = {};
		//Default language (English)
		def["name"] = "Junction Angles";
		def["settingsTitle"] = "Junction Angle settings";
		def["apply"] = "Apply";
		def["resetToDefault"] = "Reset to default";
        def["guess"] = "Estimate routing instructions";
        def["noInstructionColor"] = "Color for best continuation";
        def["keepInstructionColor"] = "Color for keep prompt";
        def["exitInstructionColor"] = "Color for exit prompt";
        def["turnInstructionColor"] = "Color for turn prompt";
        def["problemColor"] = "Color for angles to avoid";
        def["decimals"] = "Number of decimals";
        def["pointSize"] = "Base point size";

		//Finnish (Suomi)
		fi["name"] = "Risteyskulmat";
		fi["settingsTitle"] = "Rysteyskulmien asetukset";
		fi["apply"] = "Aseta";
		fi["resetToDefault"] = "Palauta";
        fi["guess"] = "Arvioi reititysohjeet";
        fi["noInstructionColor"] = "ohjeeton \"Suora\"-väri";
        fi["keepInstructionColor"] = "\"Poistu\"-ohjeen väri";
        fi["exitInstructionColor"] = "\"poistu\"-ohjeen väri";
        fi["turnInstructionColor"] = "\"Käänny\"-ohjeen väri";
        fi["problemColor"] = "Vältettävien kulmien väri";
        fi["decimals"] = "Desimaalien määrä";
        fi["pointSize"] = "Ympyrän peruskoko";

		//Swedish (Svenska)
		sv["name"] = "Korsningsvinklar";
		sv["settingsTitle"] = "Inställningar för korsningsvinklar";
		sv["apply"] = "Godkänn";
		sv["resetToDefault"] = "Återställ";
        sv["guess"] = "Gissa navigeringsinstruktioner";
        sv["noInstructionColor"] = "Färg för \"ingen instruktion\"";
        sv["keepInstructionColor"] = "Färg för\"håll höger/vänster\"-instruktion";
        sv["exitInstructionColor"] = "Färg för \"ta av\"-instruktion";
        sv["turnInstructionColor"] = "Färg för \"sväng\"-instruktion";
        sv["problemColor"] = "Färg för vinklar att undvika";
        sv["decimals"] = "Decimaler";
        sv["pointSize"] = "Cirkelns basstorlek";
		
		//Apply
		switch (I18n.locale) {
			case 'sv':
				I18n.translations['sv'].ja = sv;
				break;
			case 'fi':
				I18n.translations['fi'].ja = fi;
				break;
		}
	}

    ja_bootstrap();

}

//Dynamically create, add and run the script in the real page context. We really do need access to many of the objects...
var DLscript = document.createElement("script");
DLscript.textContent = '' +
    run_ja.toString() + ' \n' +
    'run_ja();';
DLscript.setAttribute("type", "application/javascript");
document.body.appendChild(DLscript);