WME WazeMY

WME script for WazeMY editing moderation

目前为 2022-08-05 提交的版本,查看 最新版本

// ==UserScript==
// @name         WME WazeMY
// @namespace    https://greasyfork.org/en/scripts/404584-wazemy
// @version      2022.08.06.01
// @description  WME script for WazeMY editing moderation
// @author       junyianl
// @match        https://beta.waze.com/*
// @match        https://www.waze.com/forum/*
// @match        https://www.waze.com/editor*
// @match        https://www.waze.com/*/editor*
// @match        https://www.waze.com/user/editor*
// @require      https://greasyfork.org/scripts/24851-wazewrap/code/WazeWrap.js
// @grant        GM_xmlhttpRequest
// @license      MIT
// ==/UserScript==

// @connect      fgies.com
// @connect      jalanow.com
// @connect      odis.sgp1.digitaloceanspaces.com

/* global W */
/* global WazeWrap */
/* global $ */
/* global OL */
/* global OpenLayers */

/**
 * All mentions of script names and links in this script is my way of giving 
 * credit to where it's due.
 * Without those scripts, I would have no place to start.
 * Big thanks to the original script authors.
 * 
 * Huge thanks to the following contributors for cam locations around Malaysia.
 * :: epailxi | dckj | firman_bakti | rickylo103 ::
 */

const updateMessage = "► More traffic cams. Slight update to the cam popup layout.";
var staticUpdateID;

var trafficCamsData = [
    { desc:'Jalan Sultan Ismail / Jalan Imbi near Berjaya Times Square KL', lat:3.14369, lon:101.71245, url:'https://p4.fgies.com/kl8/img/K012W.jpg' },
    { desc:'Jalan Bukit Bintang / Jalan Raja Chulan near Pavilion KL', lat:3.14833, lon:101.71609, url:'https://p4.fgies.com/kl8/img/K004W.jpg' },
    { desc:'Jalan Sultan Ismail near Sime Darby', lat:3.16102, lon:101.69522, url:'https://p4.fgies.com/kl8/img/K011W.jpg' },
    { desc:'Jalan Sultan Ismail / Jalan P. Ramlee near Shangri-La Hotel', lat:3.15389, lon:101.70761, url:'https://p4.fgies.com/kl8/img/K013W.jpg' },
    { desc:'Jalan Sultan Ismail near Renaissance Hotel', lat:3.15929307072728, lon:101.700892565152, url:'https://p4.fgies.com/kl8/img/K029W.jpg' },
    { desc:'Jalan Raja Laut near KWSP', lat:3.15314, lon:101.69473, url:'https://p4.fgies.com/kl8/img/K037W.jpg' },
    { desc:'Jalan Raja near Stesen LRT Bandaraya', lat:3.15076, lon:101.69461, url:'https://p4.fgies.com/kl8/img/K016W.jpg' },
    { desc:'Bulatan Dato\' Onn near Bank Negara', lat:3.151524, lon:101.693394, url:'https://p4.fgies.com/kl8/img/K036W.jpg' },
    { desc:'Jalan Raja near Dataran Merdeka', lat:3.15130467327156, lon:101.693128740951, url:'https://p4.fgies.com/kl8/img/K028W.jpg' },
    { desc:'Jalan Kuching / Jalan Raja Laut near Bangunan DBKL', lat:3.15328733409485, lon:101.693719456795, url:'https://p4.fgies.com/kl8/img/K001W.jpg' },
    { desc:'Jalan Maharajalela near Maharajalela Monorail Station', lat:3.13992, lon:101.69709, url:'https://p4.fgies.com/kl8/img/K038W.jpg' },
    { desc:'Jalan Kinabalu near Perpustakaan KL', lat:3.14741, lon:101.69247, url:'https://p4.fgies.com/kl8/img/K008W.jpg' },
    { desc:'Jalan Kinabalu near Masjid Negara', lat:3.14325, lon:101.69287, url:'https://p4.fgies.com/kl8/img/K007W.jpg' },
    { desc:'Jalan Kuching near Jalan Sultan Ismail', lat:3.16173, lon:101.69247, url:'https://p4.fgies.com/kl8/img/K047W.jpg' },
    { desc:'Jalan Kuching near Bulatan Kepong', lat:3.20168, lon:101.67037, url:'https://p4.fgies.com/kl8/img/K002W.jpg' },
    { desc:'Jalan Kuching near Hentian Putra / PWTC', lat:3.16693332927155, lon:101.689706476829, url:'https://p4.fgies.com/kl8/img/K046W.jpg' },
    { desc:'Jalan Kuching near Jalan Sultan Ismail', lat:3.16343529038211, lon:101.692232025972, url:'https://p4.fgies.com/kl8/img/K023W.jpg' },
    { desc:'Jalan Tun Razak near Hospital Kuala Lumpur (HKL)', lat:3.17284962099842, lon:101.704733072188, url:'https://p4.fgies.com/kl8/img/K003W.jpg' },
    { desc:'Jalan Tun Razak near U.S Embassy KL', lat:3.15365, lon:101.72267, url:'https://p4.fgies.com/kl8/img/K017W.jpg' },
    { desc:'Jalan Tun Razak near Lembaga Tabung Haji', lat:3.15779, lon:101.72083, url:'https://p4.fgies.com/kl8/img/K024W.jpg' },
    { desc:'Jalan Tuanku Abdul Halim near Taman Duta', lat:3.15073, lon:101.6746, url:'https://p4.fgies.com/kl8/img/K044W.jpg' },
    { desc:'Jalan Tuanku Abdul Halim near Jalan Semantan', lat:3.15269, lon:101.67293, url:'https://p4.fgies.com/kl8/img/K045W.jpg' },
    { desc:'Jalan Damansara near Jalan Istana Lama', lat:3.13603, lon:101.69204, url:'https://p4.fgies.com/kl8/img/K043W.jpg' },
    { desc:'Jalan Syed Putra near Sekolah Kuen Cheng', lat:3.13179, lon:101.69184, url:'https://p4.fgies.com/kl8/img/K034W.jpg' },
    { desc:'Jalan Syed Putra near Kampung Attap', lat:3.13863, lon:101.69531, url:'https://p4.fgies.com/kl8/img/K015W.jpg' },
    { desc:'Jalan Damansara near Carcosa Seri Negara', lat:3.13843, lon:101.68482, url:'https://p4.fgies.com/kl8/img/K005W.jpg' },
    { desc:'Timur–Barat Highway near Bandar Tun Razak', lat:3.09385, lon:101.71095, url:'https://p4.fgies.com/kl8/img/K041W.jpg' },
    { desc:'Timur–Barat Highway near Salak Selatan', lat:3.09785, lon:101.70352, url:'https://p4.fgies.com/kl8/img/K042W.jpg' },
    { desc:'Jalan Cheras near Bulatan Cheras', lat:3.1136, lon:101.72816, url:'https://p4.fgies.com/kl8/img/K031W.jpg' },
    { desc:'Jalan Cheras near Jalan Ikan Ayu', lat:3.11799, lon:101.72806, url:'https://p4.fgies.com/kl8/img/K025W.jpg' },
    { desc:'Bulatan Cheras near Lotus\'s Cheras', lat:3.09925, lon:101.73784, url:'https://p4.fgies.com/kl8/img/K026W.jpg' },
    { desc:'Jalan Sungai Besi Near Lapangan Terbang TUDM', lat:3.12146, lon:101.70823, url:'https://p4.fgies.com/kl8/img/K039W.jpg' },
    { desc:'Jalan Loke Yew near Tsun Jin High School', lat:3.13407, lon:101.70685, url:'https://p4.fgies.com/kl8/img/K035W.jpg' },
    { desc:'Jalan Loke Yew near Viva Shopping Mall', lat:3.11887, lon:101.72196, url:'https://p4.fgies.com/kl8/img/K009W.jpg' },
    { desc:'KL-Seremban Highway near Jalan Sungai Besi (BHP)', lat:3.12252, lon:101.70843, url:'https://p4.fgies.com/kl8/img/K010W.jpg' },
    { desc:'KL-Seremban Highway near Nirvana Memorial Center', lat:3.08873, lon:101.69675, url:'https://p4.fgies.com/kl8/img/K019W.jpg' },
    { desc:'KL-Seremban Highway near Petronas TPM', lat:3.05429, lon:101.70517, url:'https://p4.fgies.com/kl8/img/K040W.jpg' },
    { desc:'MBJB cam 01 near Persimpangan Jalan Skudai / Sutera Utama', lat:1.52143, lon:103.68009, url:'https://p4.fgies.com/bucket-mbjb/01W.jpg' },
    { desc:'MBJB cam 02 near Jalan Skudai (Hadapan Taman Johor)', lat:1.50801450501849, lon:103.689733181908, url:'https://p4.fgies.com/bucket-mbjb/02W.jpg' },
    { desc:'MBJB cam 03 near Jalan Skudai / Jalan Tun Abdul Razak', lat:1.48727160040328, lon:103.716398242299, url:'https://p4.fgies.com/bucket-mbjb/03W.jpg' },
    { desc:'MBJB cam 04 near Jalan Susur 4 / Jalan Tun Abdul Razak', lat:1.47912277436081, lon:103.752466052307, url:'https://p4.fgies.com/bucket-mbjb/04W.jpg' },
    { desc:'MBJB cam 05 near Jalan Tun Abdul Razak / Jalan Tebrau', lat:1.46585995295866, lon:103.761166576913, url:'https://p4.fgies.com/bucket-mbjb/05W.jpg' },
    { desc:'MBJB cam 06 near Jalan Persiaran Abu Bakar Sultan (Danga Bay)', lat:1.47885180802371, lon:103.723480509256, url:'https://p4.fgies.com/bucket-mbjb/06W.jpg' },
    { desc:'MBJB cam 07 near Jalan Persiaran Abu Bakar Sultan (in front of Hospital Sultanah Aminah)', lat:1.4572059284377, lon:103.747094776885, url:'https://p4.fgies.com/bucket-mbjb/07W.jpg' },
    { desc:'MBJB cam 08 near Jalan Persiaran Ismail Sultan / Jalan Ayer Molek', lat:1.45505224686676, lon:103.759729931891, url:'https://p4.fgies.com/bucket-mbjb/08W.jpg' },
    { desc:'MBJB cam 09 near KM0, Hadapan MBJB', lat:1.45538454816586, lon:103.762067556349, url:'https://p4.fgies.com/bucket-mbjb/09W.jpg' },
    { desc:'MBJB cam 10 near Jalan Wong Ah Fook / Plaza Seni', lat:1.46040674713598, lon:103.763869278212, url:'https://p4.fgies.com/bucket-mbjb/10W.jpg' },
    { desc:'MBJB cam 11 near Jalan Segget (Hadapan Bangunan MIC)', lat:1.45791001614459, lon:103.764268946504, url:'https://p4.fgies.com/bucket-mbjb/11W.jpg' },
    { desc:'MBJB cam 12 near Jalan Meldrum/ Jalan Siu Nam', lat:1.45882985226327, lon:103.765712421493, url:'https://p4.fgies.com/bucket-mbjb/12W.jpg' },
    { desc:'MBJB cam 13 near Jalan Trus / Jalan Ungku Puan', lat:1.4587307677419, lon:103.763650801401, url:'https://p4.fgies.com/bucket-mbjb/13W.jpg' },
    { desc:'MBJB cam 14 near Jalan Kempas Lama / Lebuhraya Utara Selatan', lat:1.55127414526798, lon:103.710707183628, url:'https://p4.fgies.com/bucket-mbjb/14W.jpg' },
    { desc:'MBJB cam 15 near Jalan Kempas Spur / Jalan Kempas Lama', lat:1.54622257888336, lon:103.706616462469, url:'https://p4.fgies.com/bucket-mbjb/15W.jpg' },
    { desc:'MBJB cam 16 near Persimpangan Bertingkat Jalan Kempas Baru / Pasir Gudang', lat:1.51940557823981, lon:103.727432162933, url:'https://p4.fgies.com/bucket-mbjb/16W.jpg' },
    { desc:'MBJB cam 17 near Persimpangan Bertingkat Jalan Datin Halimah / Jalan Tampoi', lat:1.5120697707288, lon:103.733145182088, url:'https://p4.fgies.com/bucket-mbjb/17W.jpg' },
    { desc:'MBJB cam 18 near Jalan Stulang Laut / Jalan Ibrahim Sultan', lat:1.46977565748046, lon:103.782259344672, url:'https://p4.fgies.com/bucket-mbjb/18W.jpg' },
    { desc:'MBJB cam 19 near Persimpangan Pesisir Perling / Jalan Tampoi Utama', lat:1.50187688096647, lon:103.686758790376, url:'https://p4.fgies.com/bucket-mbjb/19W.jpg' },
    { desc:'MBJB cam 20 near Jalan Tampoi (Hadapan Plaza Angsana)', lat:1.49594210506084, lon:103.705407431335, url:'https://p4.fgies.com/bucket-mbjb/23W.jpg' },
    { desc:'MBJB cam 21 near Persimpangan Jalan Tampoi / Jalan Ungku Mohsin', lat:1.51520533065784, lon:103.739822579285, url:'https://p4.fgies.com/bucket-mbjb/21W.jpg' },
    { desc:'MBJB cam 22 near Persimpangan Jalan Kota Tinggi / Jalan Ulu Tiram', lat:1.59905398228792, lon:103.820736374127, url:'https://p4.fgies.com/bucket-mbjb/22W.jpg' },
    { desc:'MBJB cam 23 near Persimpangan bertingkat Jalan Kota Tinggi / Persiaran Desa Tebrau', lat:1.5491552104896, lon:103.798860796817, url:'https://p4.fgies.com/bucket-mbjb/23W.jpg' },
    { desc:'MBJB cam 24 near Jalan Kota Tinggi (Hadapan Pasar Borong Pandan)', lat:1.52271947244132, lon:103.766539983458, url:'https://p4.fgies.com/bucket-mbjb/24W.jpg' },
    { desc:'MBJB cam 25 near Jalan Tebrau / Jalan Rebung', lat:1.512418555786, lon:103.759900352551, url:'https://p4.fgies.com/bucket-mbjb/25W.jpg' },
    { desc:'MBJB cam 26 near Jalan Tebrau (Hadapan Wisma Daiman)', lat:1.49348545231193, lon:103.766636743807, url:'https://p4.fgies.com/bucket-mbjb/26W.jpg' },
    { desc:'MBJB cam 27 near Jalan Tebrau / Jalan Keris', lat:1.48713839354697, lon:103.766427939883, url:'https://p4.fgies.com/bucket-mbjb/27W.jpg' },
    { desc:'MBJB cam 28 near Jalan Tebrau (Metropolis Tower)', lat:1.47860201699811, lon:103.763678135017, url:'https://p4.fgies.com/bucket-mbjb/28W.jpg' },
    { desc:'MBJB cam 29 near Persimpangan Lebuhraya Pasir Gudang /Seri Alam', lat:1.50385805346979, lon:103.850538399541, url:'https://p4.fgies.com/bucket-mbjb/29W.jpg' },
    { desc:'MBJB cam 30 near Jalan Tun Razak / Susur 4 (Hadapan Courts)', lat:1.48512, lon:103.74855, url:'https://p4.fgies.com/bucket-mbjb/30W.jpg' },
    { desc:'MBJB cam 31 near Persimpangan Jalan Pasir Pelangi / Jalan Bakar Batu', lat:1.49518256528262, lon:103.782900748942, url:'https://p4.fgies.com/bucket-mbjb/31W.jpg' },
    { desc:'MBJB cam 32 near Lingkaran Dalam / Stulang Darat', lat:1.46819491027982, lon:103.769451244357, url:'https://p4.fgies.com/bucket-mbjb/32W.jpg' },
    { desc:'MBJB cam 33 near Jalan Datin Halimah / Jalan Kenyalang', lat:1.49436, lon:103.73966, url:'https://p4.fgies.com/bucket-mbjb/33W.jpg' },
    { desc:'MBJB cam 35 near Persiaran Kempas Baru / Jalan Banjaran Utama', lat:1.52955, lon:103.71108, url:'https://p4.fgies.com/bucket-mbjb/35W.jpg' },
    { desc:'LPT 2 near 01 Perasing R&R KM259.870 NB', lat:4.01753, lon:103.313694, url:'https://p3.fgies.com/bucket-ece2/ECE2-01.jpg' },
    { desc:'LPT 2 near 02 Perasing R&R KM259.870 SB', lat:4.018416, lon:103.314777, url:'https://p3.fgies.com/bucket-ece2/ECE2-02.jpg' },
    { desc:'LPT 2 near 03 Perasing R&R KM259.870 NB', lat:4.019428, lon:103.31341, url:'https://p3.fgies.com/bucket-ece2/ECE2-03.jpg' },
    { desc:'LPT 2 near 04 PTZ CNH IC KM273.4 SB', lat:4.133594, lon:103.282245, url:'https://p3.fgies.com/bucket-ece2/ECE2-40.jpg' },
    { desc:'LPT 2 near 05 Cheneh Interchange KM273.370 ENT', lat:4.127079, lon:103.266866, url:'https://p3.fgies.com/bucket-ece2/ECE2-04.jpg' },
    { desc:'LPT 2 near 06 Cheneh Toll KM273.370 EXT', lat:4.127177, lon:103.26694, url:'https://p3.fgies.com/bucket-ece2/ECE2-05.jpg' },
    { desc:'LPT 2 near 07 Cukai Interchange KM287.1 SB', lat:4.245774, lon:103.312721, url:'https://p3.fgies.com/bucket-ece2/ECE2-06.jpg' },
    { desc:'LPT 2 near 10 PTZ KJLL KM296.9 SB', lat:4.330917, lon:103.30279, url:'https://p3.fgies.com/bucket-ece2/ECE2-41.jpg' },
    { desc:'LPT 2 near 11 Kijal R&R KM296.9 NB', lat:4.332806, lon:103.302717, url:'https://p3.fgies.com/bucket-ece2/ECE2-09.jpg' },
    { desc:'LPT 2 near 12 Kijal Interchange KM305.9 NB', lat:4.385512, lon:103.356951, url:'https://p3.fgies.com/bucket-ece2/ECE2-10.jpg' },
    { desc:'LPT 2 near 13 Kijal Toll KM306.000 ENT', lat:4.376366, lon:103.36256, url:'https://p3.fgies.com/bucket-ece2/ECE2-11.jpg' },
    { desc:'LPT 2 near 14 Kijal Toll KM306.000 EXT', lat:4.376438, lon:103.362822, url:'https://p3.fgies.com/bucket-ece2/ECE2-12.jpg' },
    { desc:'LPT 2 near 15 Kertih Toll KM319.195 ENT', lat:4.481294, lon:103.407874, url:'https://p3.fgies.com/bucket-ece2/ECE2-13.jpg' },
    { desc:'LPT 2 near 16 Kertih Toll KM319.195 EXT', lat:4.481503, lon:103.408069, url:'https://p3.fgies.com/bucket-ece2/ECE2-14.jpg' },
];

(function() {
    'use strict';
    var settings = {};

    function bootstrap(tries = 1) {
        if (W && W.map && W.model && W.loginManager.user &&
            $ && WazeWrap.Ready) {
            init();
        } else if (tries < 1000) {
            setTimeout(function () {bootstrap(++tries);}, 200);
        } else {
            WazeWrap.Alerts.error(GM_info.script.name, "Bootstrap timeout.")
        }
    }

    async function init() {
        let $section = $('<div>');
        $section.html([
            '<div>',
            'Version: <span id="wazemyVersion"></span><br>',
            '<span id="wazemyUsername"></span> (<span id="wazemyRank"></span>)',
            '</div><br>',
            '<div id="wazemySettings">',
            '<b>Settings</b><br>',
            '<input type="checkbox" id="wazemySettings_tooltip">',
            '<label for="wazemySettings_tooltip">Map tooltip</label><br>',
            '<input type="checkbox" id="wazemySettings_trafcam">',
            '<label for="wazemySettings_trafcam">Traffic cameras</label><br>',
            '</div><br>',
            '<div>',
            '<b>Shortcuts</b><br>',
            'Ctrl+Alt+C: Copy lat/lon of mouse position to clipboard.<br>',
            '</div>'
        ].join(' '));
        
        // Initialize features of WME WazeMY
        wazemyTooltip_init();
        wazemyTrafcam_init();

        new WazeWrap.Interface.Tab('WazeMY', $section.html(), initializeSettings);
        WazeWrap.Interface.ShowScriptUpdate("WME WazeMY", GM_info.script.version, 
            updateMessage, "https://greasyfork.org/en/scripts/404584-wazemy", null);

        // Initialize keyboard shortcuts
        new WazeWrap.Interface.Shortcut('WazeMY_latloncopy', 
            'Copies lat/lon of mouse position', 'wazemy', 'WazeMY', 'CA+c', 
            wazemyCopyLatLon, null).add();
    }

    /* ******* */
    /* Tooltip */
    /* ******* */
    function wazemyTooltip_init() {
        let $tooltip = $('<div/>');
        $tooltip.attr('id', 'wazemyTooltip');
        $tooltip.css({
            'height':'auto',
            'width':'auto',
            'background':'rgba(0,0,0,0.5)',
            'color':'white',
            'borderRadius':'5px',
            'padding':'5px',
            'position':'absolute',
            'top':'0px',
            'left':'0px',
            'visibility':'hidden',
            'zIndex':'10000'
        })
        $tooltip.appendTo('body');
    }

    function wazemyTooltip_initSettings() {
        setChecked('wazemySettings_tooltip', settings.tooltip);
        if (settings.tooltip) {
            WazeWrap.Events.register('mousemove', null, wazemyTooltip_show);
        }
        $('#wazemySettings_tooltip').change(function () {
            var settingName = $(this)[0].id.substr(15); // strip off the "wazemySettings_" prefix
            settings[settingName] = this.checked;
            saveSettings();
            if (this.checked) {
                WazeWrap.Events.register('mousemove', null, wazemyTooltip_show);
            }
            else {
                WazeWrap.Events.unregister('mousemove', null, wazemyTooltip_show);
            }
        });
    }

    function wazemyTooltip_show(e) { // from URO+
        var result = '';
        var showTooltip = false;

        let landmark = W.map.venueLayer.getFeatureBy('renderIntent', 'highlight');
        let segment = W.map.segmentLayer.getFeatureBy('renderIntent', 'highlight');

        if (landmark != null) {
            result = '<b>' + landmark.model.attributes.name + '</b><br>';
            let address = landmark.model.getAddress();
            try {
                result += address.attributes.houseNumber ? (address.attributes.houseNumber + ', ') : ''
                result += (address.attributes.street.name ? address.attributes.street.name : 'No street') + '<br>';
                result += address.attributes.city.attributes.name + ', ';
                result += address.attributes.state.name + '<br>';
            }
            catch {
                result += 'No address<br>';
            }
            result += '<b>Lock:</b> ' + (landmark.model.getLockRank() + 1);
            showTooltip = true;
        } else if (segment != null) {
            let segmentId = segment.model.attributes.id;
            // let primaryStreetId = WazeWrap.Model.getPrimaryStreetId(segmentId);
            let address = segment.model.getAddress();
            result = '<b>';
            result += address.attributes.street.name ? address.attributes.street.name : 'No street';
            result += '</b><br>';
            result += address.attributes.city.attributes.name + ', ' + address.attributes.state.name;
            result += '<br>';
            result += '<b>ID:</b> ' + segmentId + '<br>';
            result += '<b>Lock:</b> ' + (segment.model.getLockRank() + 1);
            showTooltip = true;
        }

        if (showTooltip == true) {
            let tw = $('#wazemyTooltip').width();
            let th = $('#wazemyTooltip').height();
            var tooltipX = e.clientX + window.scrollX + 15;
            var tooltipY = e.clientY + window.scrollY + 15;
            if ((tooltipX + tw) > W.map.$map.innerWidth()) {
                tooltipX -= tw + 20; // 20 = scroll bar size
                if (tooltipX < 0) tooltipX = 0;
            }
            if ((tooltipY + th) > W.map.$map.innerHeight()) {
                tooltipY -= th + 20;
                if (tooltipY < 0) tooltipY = 0;
            }
            $('#wazemyTooltip').html(result);
            $('#wazemyTooltip').css({
                'top':tooltipY + 'px',
                'left':tooltipX + 'px',
                'visibility':'visible'
            });
        } else {
            $('#wazemyTooltip').css('visibility', 'hidden');
        }
    }

    /* *************** */
    /* Traffic cameras */
    /* *************** */
    // Adapted from WME DOT Cameras
    var wazemyTrafcamLayer;

    function wazemyTrafcam_init() {
        if (!OpenLayers.Icon) {
            wazemyTrafcam_installIconClass();
        }
        wazemyTrafcamLayer = new OpenLayers.Layer.Markers("wazemyTrafcamLayer");
        W.map.addLayer(wazemyTrafcamLayer);
        wazemyTrafcam_show();
        wazemyTrafcamLayer.setVisibility(false);
    }

    function wazemyTrafcam_initSettings() {
        setChecked('wazemySettings_trafcam', settings.trafcam);
        if (settings.trafcam) {
            wazemyTrafcamLayer.setVisibility(true);
            // WazeWrap.Events.register('moveend', null, wazemyTrafcam_show);
        }
        $('#wazemySettings_trafcam').change(function () {
            var settingName = $(this)[0].id.substr(15); // strip off the "wazemySettings_" prefix
            settings[settingName] = this.checked;
            saveSettings();
            if (this.checked) {
                wazemyTrafcamLayer.setVisibility(true);
                // WazeWrap.Events.register('moveend', null, wazemyTrafcam_show);
            } else {
                // WazeWrap.Events.unregister('moveend', null, wazemyTrafcam_show);
               wazemyTrafcamLayer.setVisibility(false);
            }
        });
    }

    function wazemyTrafcam_show(e) {
        trafficCamsData.forEach(e => {
            wazemyTrafcam_drawCam({
                desc:e.desc,
                src:e.url,
                width:20,
                height:20,
                lat:e.lat,
                lon:e.lon
            });
        });
    }

    function wazemyTrafcam_drawCam(spec) {
        const camIcon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAAGXcA1uAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA2ZpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYxIDY0LjE0MDk0OSwgMjAxMC8xMi8wNy0xMDo1NzowMSAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpBRDNGNTkwRTYzQThFMzExQTc4MDhDNjAwODdEMzdEQSIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDo2OUI0RUEyN0IwRjcxMUUzOERFM0E1OTJCRUY3NTFBOCIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDo2OUI0RUEyNkIwRjcxMUUzOERFM0E1OTJCRUY3NTFBOCIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1LjEgV2luZG93cyI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjZGOEJBMzExNkZCMEUzMTFCOEY5QTU3QUQxM0M2MjI5IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOkFEM0Y1OTBFNjNBOEUzMTFBNzgwOEM2MDA4N0QzN0RBIi8+IDwvcmRmOkRlc2NyaXB0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';
        let size = new OpenLayers.Size(20,20);
        let icon = new OpenLayers.Icon(camIcon, size);
        let epsg4326 = new OpenLayers.Projection("EPSG:4326"); // WGS 1984 projection. Malaysia uses EPSG:900913
        let projectTo = W.map.getProjectionObject();
        let lonLat = new OpenLayers.LonLat(spec.lon, spec.lat).transform(epsg4326, projectTo);
        var newMarker = new OpenLayers.Marker(lonLat, icon);
        newMarker.title = spec.desc;
        newMarker.url = spec.src;
        newMarker.width = spec.width;
        newMarker.height = spec.height;
        newMarker.location = lonLat;
        newMarker.events.register('click', newMarker, wazemyTrafcam_popupCam);
        wazemyTrafcamLayer.addMarker(newMarker);
    }

    function wazemyTrafcam_popupCam(e) {
        console.log("wazemyTrafcam_popupCam");

        clearInterval(staticUpdateID);
        $("#gmPopupContainerCam").remove();
        $("#gmPopupContainerCam").hide();

        var popupHTML = ([`
            <div id="gmPopupContainerCam" style="margin: 1;text-align: center;padding: 5px;z-index: 1100; position: absolute; color: white; background: rgba(0,0,0,0.5)">
            <table border=0>
                <tr>
                    <td><div id="mycamdivheader" style="min-height: 20px;white-space: pre-wrap;white-space: -moz-pre-wrap; white-space: -pre-wrap;white-space: -o-pre-wrap;word-wrap: break-word;width:380px">${this.title}</div></td>
                    <td align="right"><a href="#close" id="gmCloseCamDlgBtn" title="Close" style="color:red">X</a></td>
                </tr>
                <tr><td colspan=2><img style="width:400px" id="staticimage"></td></tr>
            </table></div>
        `]);
        $("body").append(popupHTML);
        GM_xmlhttpRequest({
            method: 'GET',
            responseType: 'blob',
            headers: {
                "authority":"p4.fgies.com",
                "referer":'https://www.jalanow.com/',
                'accept':'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8'
            },
            url: this.url,
            onload: function (response) {
                document.getElementById('staticimage').src = URL.createObjectURL(response.response);
            }
        });
        $("#gmPopupContainerCam").css({ left: e.clientX + window.scrollX + 15 });
        $("#gmPopupContainerCam").css({ top: e.clientY + window.scrollY + 15 });

        //Add listener for popup's "Close" button
        $("#gmCloseCamDlgBtn").click(function () {
            clearInterval(staticUpdateID);
            $("#gmPopupContainerCam").remove();
            $("#gmPopupContainerCam").hide();
        });
        wazemyTrafcam_dragElement(document.getElementById("gmPopupContainerCam"));
    }

    function wazemyTrafcam_installIconClass() {
        OpenLayers.Icon = OpenLayers.Class({
            url: null,
            size: null,
            offset: null,
            calculateOffset: null,
            imageDiv: null,
            px: null,
            initialize: function (a, b, c, d) {
                this.url = a;
                this.size = b || {
                    w: 20,
                    h: 20
                };
                this.offset = c || {
                    x: -(this.size.w / 2),
                    y: -(this.size.h / 2)
                };
                this.calculateOffset = d;
                a = OpenLayers.Util.createUniqueID("OL_Icon_");
                let div = this.imageDiv = OpenLayers.Util.createAlphaImageDiv(a);
                $(div.firstChild).removeClass('olAlphaImg'); // LEAVE THIS LINE TO PREVENT WME-HARDHATS SCRIPT FROM TURNING ALL ICONS INTO HARDHAT WAZERS --MAPOMATIC
            },
            destroy: function () {
                this.erase();
                OpenLayers.Event.stopObservingElement(this.imageDiv.firstChild);
                this.imageDiv.innerHTML = "";
                this.imageDiv = null;
            },
            clone: function () {
                return new OpenLayers.Icon(this.url, this.size, this.offset, this.calculateOffset);
            },
            setSize: function (a) {
                null !== a && (this.size = a);
                this.draw();
            },
            setUrl: function (a) {
                null !== a && (this.url = a);
                this.draw();
            },
            draw: function (a) {
                OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, this.size, this.url, "absolute");
                this.moveTo(a);
                return this.imageDiv;
            },
            erase: function () {
                null !== this.imageDiv && null !== this.imageDiv.parentNode && OpenLayers.Element.remove(this.imageDiv);
            },
            setOpacity: function (a) {
                OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, null, null, null, null, null, null, a);
            },
            moveTo: function (a) {
                null !== a && (this.px = a);
                null !== this.imageDiv && (null === this.px ? this.display(!1) : (
                    this.calculateOffset && (this.offset = this.calculateOffset(this.size)),
                    OpenLayers.Util.modifyAlphaImageDiv(this.imageDiv, null, {
                        x: this.px.x + this.offset.x,
                        y: this.px.y + this.offset.y
                    })
                ));
            },
            display: function (a) {
                this.imageDiv.style.display = a ? "" : "none";
            },
            isDrawn: function () {
                return this.imageDiv && this.imageDiv.parentNode && 11 != this.imageDiv.parentNode.nodeType;
            },
            CLASS_NAME: "OpenLayers.Icon"
        });
    }

    // Make the DIV element draggable:
    function wazemyTrafcam_dragElement(elmnt) {
        var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
        if (document.getElementById("mycamdivheader")) {
            // if present, the header is where you move the DIV from:
            document.getElementById("mycamdivheader").onmousedown = dragMouseDown;
        } else {
            // otherwise, move the DIV from anywhere inside the DIV:
            elmnt.onmousedown = dragMouseDown;
        }

        function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();
            // get the mouse cursor position at startup:
            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            // call a function whenever the cursor moves:
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();
            // calculate the new cursor position:
            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;
            // set the element's new position:
            elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
            elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
        }

        function closeDragElement() {
            // stop moving when mouse button is released:
            document.onmouseup = null;
            document.onmousemove = null;
        }
    }

    /* ************ */
    /* Copy lat/lon */
    /* ************ */
    function wazemyCopyLatLon(){
        copyToClipboard($('.mouse-position').text());
    }

    function initializeSettings() {
        loadSettings();

        $('#wazemyVersion').text(GM_info.script.version);
        $('#wazemyUsername').text(WazeWrap.User.Username());
        $('#wazemyRank').text(WazeWrap.User.Rank());

        wazemyTooltip_initSettings();
        wazemyTrafcam_initSettings();
    }

    function saveSettings() {
        if (localStorage) {
            var localsettings = {
                tooltip: settings.tooltip,
                trafcam: settings.trafcam
            };

            localStorage.setItem('WME_wazemySettings', JSON.stringify(localsettings));
        }
    }

    function loadSettings() {
        var loadedSettings = $.parseJSON(localStorage.getItem("WME_wazemySettings"));
        var defaultSettings = {
            tooltip: false,
        };
        settings = loadedSettings ? loadedSettings : defaultSettings;
        for (var prop in defaultSettings) {
            if (!settings.hasOwnProperty(prop)) {
                settings[prop] = defaultSettings[prop];
            }
        }
    }

    function setChecked(checkboxId, checked) {
        $('#' + checkboxId).prop('checked', checked);
    }

    // utility functions
    var copyToClipboard = function(str) { // from PIE
        var $temp = $('<input>');
        $('body').append($temp);
        $temp.val(str).select();
        document.execCommand('copy');
        $temp.remove();
    };

    bootstrap();
})();