mhulse / kludgy

Kludgy: A macOS Node.js module/app that generates a random Google Street View desktop wallpaper.
Apache License 2.0
4 stars 2 forks source link

Improve randomness: If not above land (or near it) then try again (or, other technique?) #17

Closed mhulse closed 5 years ago

mhulse commented 7 years ago

I think i need to give it ramdomness withing countries.

mhulse commented 7 years ago

https://stackoverflow.com/questions/9104908/random-geographic-coordinates-on-land-avoid-ocean

http://jsfiddle.net/erickerr/4uj8m/2/

mhulse commented 5 years ago

Here's an alternative/simpler way of getting rand lat/lon:

<script src="https://maps.googleapis.com/maps/api/js?v=3&key=xxxxxx"></script>
<script>
function TryRandomLocation(callback) {
  //var lat = (Math.random() * 90) - 90;
  var lat = Math.random() * Math.max((180 - 0 * .25), 120) - (90 - Math.min(0 * .25, 45));
  var lng = Math.random() * 360 - 180;
  var sv = new google.maps.StreetViewService();

  // Try to find a panorama within 50 metres 
  sv.getPanorama({
      location: new google.maps.LatLng(lat, lng),
      radius: 50
  }, callback);
}

function HandleCallback(data, status) {
    if (status == 'OK') {
      // Call your code to display the panorama here.
      console.log(data)
    } else {
        console.log('Trying again')
      // Nothing here! Let's try another location.
      TryRandomLocation(HandleCallback);
    }
}

window.onload = function() {
  TryRandomLocation(HandleCallback);
}
</script>

https://stackoverflow.com/a/44478605/922323

Might need to rethink my approach.

Some have suggested working with bounds of a country as the earth has a lot of ocean.

mhulse commented 5 years ago

Some good info here:

https://stackoverflow.com/questions/990148/how-to-add-random-markers-to-a-map-but-avoiding-the-sea

Good to mention that I got a lot of the original code here:

http://vispo.com/go/index.htm

/* Teleporter by Jim Andrews, March 2014. All rights reserved.
See vispo.com for contact details.

Transports the user to a random location on the planet at the click
of a button. No, wait. What it actually does is generate a random
latitude and a random longitude and then find the closest Google 
maps panorama to that random LatLng. If we haven't visited that 
panorama, we visit it. If we have, then we select a different 
random LatLng and get the closest panorama to it, etc. */

//************************************************************
// GLOBAL VARIABLES
//************************************************************
var webService, panorama, map, marker, marker2;
// panorama is a Google Maps StreetViewPanorama object.
// map is a Google Maps map object.
// marker is a Google Marker object; the red map marker.
// marker2 is the cross-hair image on the map. 
var MY_MAPTYPE_ID = 'custom_style';
// for the custom noir map.
var locations=[];
// array of LatLng locations we have seen panoramas at.
var infoWindow;
// displays the address when you click the marker.
var geocoder;
// used to find the address from a LatLng.
var URLparams = new URLparameters();
var URLparamsToGo = new URLparameters(0);
// objects for URL parameters. Used for 'share' button.
var teleportCounter=0;
// Counts the number of clicks on the Teleport button

//************************************************************
// PROCESS ANY PARAMETERS ATTACHED TO THE URL
//************************************************************

function getInitialInfoFromParams() {
    // Called in function initialize to get info from URLparams.
    // Returns an object containing URL params latLng, heading,
    // pitch, and zoom. These exist when Teleport is being 
    // started up from a sharing of a panorama. 
    var numOfParams=URLparams.length()
    if (numOfParams) {
        var lat=URLparams.getValue('la');
        var lon=URLparams.getValue('lo');
        var pitch=parseInt(URLparams.getValue('pi'));
        var heading=parseInt(URLparams.getValue('he'));
        var zoom=parseInt(URLparams.getValue('zo'));
        if (lat && lon) {
            var loc=new google.maps.LatLng(lat,lon);
        }
        else {
            var loc=false;
        }
        return {latLng:loc, heading:heading, pitch:pitch, zoom:zoom}
    }
    else {
        return false;
    }
}

//************************************************************
// SHARE BUTTON
//************************************************************

function getAddressAndURL() {
    // Runs when the user clicks the Share button.
    var latlng=panorama.getPosition();
    geocoder.geocode({'latLng': latlng}, writeAddressAndURL);
    // geocode gets the address of latlng.
    function writeAddressAndURL(results, status) {
        // writeAddressAndURL runs when geocode has results.
        if (status == google.maps.GeocoderStatus.OK) {
            if (results[0]) {
                var string= results[0].formatted_address + '\n\n' + createURLToShare();
                openShareDialog(string);
            } else {
                var string= createURLToShare();
                openShareDialog(string);
            }
        } else {
            var string= createURLToShare();
            openShareDialog(string);
        }
    }
}

function createURLToShare() {
    // Called by getAddressAndURL when the user clicks the Share.
    // button. Manufactures a URL which, when pasted into a
    // browser, starts Teleport at the current location.
    var currentCenter=panorama.getPosition();
    var pov=panorama.getPov();
    var heading = pov.heading;
    var pitch= pov.pitch;
    var zoom=panorama.getZoom();
    var URLparamsToGo = new URLparameters(0);
    URLparamsToGo.add(['la',currentCenter.lat().toFixed(6)])
    URLparamsToGo.add(['lo',currentCenter.lng().toFixed(6)]);
    URLparamsToGo.add(['he',heading.toFixed(2)]);
    URLparamsToGo.add(['pi',pitch.toFixed(2)]);
    URLparamsToGo.add(['zo',zoom]);
    return URLparamsToGo.getFullyConstructedURLWithParameters();
}

function openShareDialog(addressAndURL) {
    var s="Copy and paste to share:\n\n" + addressAndURL;
    alert(s);
}

//************************************************************
// TELEPORTER BUTTON FUNCTIONS
//************************************************************

function clickTeleportButton() {
    // Called when the user clicks the Teleport button.
    randomPanorama();
    teleportCounter++;
    document.getElementById('teleporter').innerHTML= 'Teleport ' + teleportCounter;
} // End of clickTeleportButton
//------------------------------------------------------------

function randomPanorama() {
    // Generates a random location. Then it finds the closest
    // panorama to that random location. 
    var randomLocation = randomLatLng();
    // randomLocation is a random location on planet Earth.
    // randomLocation is of type LatLng.
    setNearestPanorama(randomLocation, 50, false);
    // Finds the nearest Google panorama to randomLocation
    // and then displays that panorama.
} // End of randomPanorama
//------------------------------------------------------------

function randomLatLng() {
        // Returns a random location on the planet as a LatLng.
        var randomLatitude = Math.random()*Math.max((180-teleportCounter*0.25), 120) - (90 - Math.min(teleportCounter*0.25, 45));
        // randomLatitude starts out as a random number between -90 and 90.
        // Then we slowly decrease the range from 180 to 120 cuz there are
        // 60 degrees of antarctica and the arctic where we have few photos.
        var randomLongitude = Math.random() * 360 - 180;
        // randomLongitude is a random number between -180 and 180.
        return new google.maps.LatLng(randomLatitude, randomLongitude);
}  // End of randomLatLng
//------------------------------------------------------------

function setNearestPanorama(latLng, bounds, allowRepeatedVisits) {
    /* This function finds the closest panorama to the supplied
    latLng parameter, within the specified bounds (in meters). 
    If no panorama exists within those bounds, we increase the 
    bounds by a factor of 2 and recursively call setNearestPanorama
    with the same latLng and allowRepeatedVisits parameters. If, on
    the other hand, we find the closest panorama within the specified
    bounds then, if we have not visited it, we display it and add
    it to the list of visited panoramas. If we already have visited
    it, then, if allowRepeatedVisits is true, we display it, but if
    allowRepeatedVisits is false, we select a new random latLng and
    call setNearestPanorama with bounds set to 50 and allowRepeatedVisits
    set to false. */
    var checkaround = bounds || 50;
    // The function will search within checkaround meters of latLng.
    webService.getPanoramaByLocation(latLng, checkaround, checkNearestStreetView);
    /* checkNearestStreetView is a callback function. That is, it 
    gets called when getPanoramaByLocation has results */
    //``````````````````````````````````````````````````````````````
    function checkNearestStreetView(panoData) {
        // Called when getPanoramaByLocation has results. Could have
        // timed out with no results or have a panorama for us.
        var found=false;
        if (panoData) {
             if (panoData.location) {
                    if (panoData.location.latLng) {
                        // We have a nearest streetview panorama.
                        found=true;
                    }
             }
        }
        if (found) {
            // We have a nearest panorama. 
            var loc=panoData.location.latLng;
            if (allowRepeatedVisits || !alreadyVisited(loc)) {
                // If repeated visits are allowed or if we have not
                // already visited the panorama, we display it.
                panorama.setPosition(loc);
                map.setCenter(loc);
                marker.setPosition(loc);
                var panoramaIsVisible = panorama.getVisible();
                if ( !panoramaIsVisible) toggleStreetView();
                // Set the panorama!
            }
            else {
                // If we do not allow repeated visits to the same panorama
                // and we have already visited this panorama, we start the
                // process of selecting a panorama from the beginning.
                randomPanorama();
            }
        }
        else {
            setNearestPanorama(latLng, checkaround*2, allowRepeatedVisits);
            // We have not found a streetview panorama within checkaround
            // meters of latLng. So we recursively call setNearestPanorama
            // with larger bounds, ie, we search a larger circle or area.
        }
        //...............................................................
        function alreadyVisited(pos) {
            /*  Defined within setNearestPanorama. alreadyVisited is called
            by setNearestPanorama to determine if we have already visited a
            location. pos is a LatLng. alreadyVisited returns a boolean.
            Returns true if we have already visited the location, false
            otherwise. The locations array is an array of LatLng objects
            that we have already visited this session. */
            var notFound=true;
            // notFound is a boolean that's true while we have not found
            // a location that is really close to pos.
            var i=0;
            while (notFound && i<locations.length) {
                // While we have not found a location in the list that is
                // really close to pos and we have not looked at all the
                // LatLng values in the list, continue looping.
                var ob=locations[i];
                var areEqual = equalLatLngs(pos,ob);
                if (areEqual) {
                    notFound=false;
                }
                else {
                    i++;
                }
            }
            if (notFound) {
                locations.push(pos);
            }
            return !notFound;
        }
        //...............................................................
        function equalLatLngs(o1,o2) {
            // Returns true if the distance between the two LatLng objects o1
            // and o2 is less than 10 meters. Returns false otherwise.
            if (google.maps.geometry.spherical.computeDistanceBetween(o1,o2) < 10) {
                return true
            }
            else {
                return false;
            }
        }
    } // end of checkNearestStreetView
} // end of setNearestPanorama
//------------------------------------------------------------

//************************************************************
// TOGGLE BUTTON
//************************************************************

function toggleStreetView() {
        // Called when the user clicks the button to go from
        // Street View to Map View or Map View to Street View.
        var panoramaIsVisible = panorama.getVisible();
        if (panoramaIsVisible == false) {
            // We move from map view to panorama view.
            var currentCenter=map.getCenter();
            // currentCenter is the current center of the map.
            setNearestPanorama(currentCenter, 50, true);
            // Display the closest panorama to the current center.
            // If we've already seen it, that's OK, display it anyway.
            panorama.setVisible(true);
            document.getElementById('butt1').innerHTML='Map';
            // Set the button to say 'Map'.
        } else {
            // We move from panorama view to map view.
            var currentCenter=panorama.getPosition();
            map.setCenter(currentCenter);
            marker.setPosition(currentCenter);
            setAddress(currentCenter);
            marker2.setVisible(false);
            panorama.setVisible(false);
            document.getElementById('butt1').innerHTML='Street';
        }
    }

    function setAddress(latlng) {
        // Sets the address you see when you click the marker on the map.
        if (latlng) {
            // Then we have a location
            geocoder.geocode({'latLng': latlng}, function(results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    if (results[0]) {
                        infoWindow.setContent(results[0].formatted_address);
                    } else {
                        infoWindow.setContent('Address unavailable. Address status: ' + status);
                    }
                } else {
                    infoWindow.setContent('Address unavailable. Address status: ' + status);
                }
            });
        }
        else {
            // In this case latlng is undefined.
            infoWindow.setContent('Location undefined.');
        }
    infoWindow.close();
    }

//************************************************************
// INITIALIZATION
//************************************************************

function initialize() {
    // Called at the beginning of the program after the window has loaded.
    webService = new google.maps.StreetViewService(); 
    geocoder = new google.maps.Geocoder(); 
    var initialLatLng = new google.maps.LatLng(51.492177,-0.193015);
    // See http://tinyurl.com/lroeh5w for info on the opening panorama.
    var initialHeading=130;
    var initialPitch=0;
    var initialZoom=1;
    var initialInfo=getInitialInfoFromParams();
    // If the session involves URL parameters, the 
    // params will be in initialInfo.
    if (initialInfo) {
        // If there are URL parameters, then they will 
        // at least include a latLng, so set it.
        initialLatLng=initialInfo.latLng;
        if (initialInfo.heading) {
            initialHeading=initialInfo.heading;
        }
        if (initialInfo.pitch) {
            initialPitch=initialInfo.pitch;
        }
        if (initialInfo.zoom) {
            initialZoom=initialInfo.zoom;
        }
    }
    /*-------------------------------------------------------------- 
    See http://tinyurl.com/l3xs7v2 for info on panorama options. */
    var panoramaOptions = {
        position: initialLatLng,
        pov: {
            heading: initialHeading,
            pitch: initialPitch
        },
        addressControl:false,
        /* addressControlOptions: {
            position: google.maps.ControlPosition.BOTTOM
    }, */
    };
    //----------------------------------------------------------------
    // featureOpts styles the noir map.
    var featureOpts = [
        {
            stylers: [ 
                { hue: '#777777' },
                { gamma: 0.3},
                { weight: 1 },
                { saturation: -100},
                {lightness: -30}
            ]
        },
        {
            elementType: 'labels',
            stylers: [
                { visibility: 'on' }
            ]
        },
        {
            featureType: 'water',
            stylers: [
                { color: '#333333' }
            ]
        }
    ];
    //-----------------------------------------------------------------
    var mapOptions = {
        zoom: 12,
        center: initialLatLng,
        mapTypeControlOptions: {
            mapTypeIds: [MY_MAPTYPE_ID, google.maps.MapTypeId.ROADMAP, google.maps.MapTypeId.SATELLITE]
        },
        mapTypeId: MY_MAPTYPE_ID,
        streetViewControl:false
    };
    //------------------------------------------------------------------            
    var el=document.getElementById('pano'); // the panorama and map div
    //------------------------------------------------------------------        
    map = new google.maps.Map(el, mapOptions); // create the map!
    var styledMapOptions = {
        name: 'Noir'
    };
    //------------------------------------------------------------------        
    // now we'll style the map.
    var customMapType = new google.maps.StyledMapType(featureOpts, styledMapOptions);
    map.mapTypes.set(MY_MAPTYPE_ID, customMapType);
    //------------------------------------------------------------------
    // now create the location marker on the map
    marker=new google.maps.Marker({
        position:initialLatLng,
    });
    marker.setMap(map); 
    //------------------------------------------------------------------        
    // the info window opens when you click the location marker.
    infoWindow = new google.maps.InfoWindow({content: "A Tardis"}); 
    google.maps.event.addListener(marker, 'click', function() {
    infoWindow.open(map,marker);
  });
    infoWindow.close();
    //------------------------------------------------------------------        
    // the cross-hair is used to pinpoint where you will go in street view
    var crosshairShape = {coords:[0,0,0,0],type:'rect'};
    marker2 = new google.maps.Marker({
        map: map,
        icon: 'images/cross-hairs.gif',
        shape: crosshairShape,
        zIndex: 99999999
    });
    marker2.bindTo('position', map, 'center'); 
    // The crosshairs stays in the center of the map.
    // bindTo is a method of MVCObjects.
    // make the cross-hair visible when the user clicks the map.
    map.addListener('mousedown', makeCrosshairVisible);
    function makeCrosshairVisible() {marker2.setVisible(true)};
    //------------------------------------------------------------------        
    panorama = new  google.maps.StreetViewPanorama(el,panoramaOptions);
    panorama.setZoom(initialZoom);
    // panorama is initially visible
    //----------------------------------------------------------------
    // Button styling.
    document.getElementById('butt1').onmouseover=buttHover;
    document.getElementById('butt1').onmouseout=buttLeave;
    document.getElementById('teleporter').onmouseover=buttHover;
    document.getElementById('teleporter').onmouseout=buttLeave;
    document.getElementById('credits').onmouseover=buttHover;
    document.getElementById('credits').onmouseout=creditsLeave;

    function buttHover() {
        this.style.color='red';
    }

    function buttLeave() {
        this.style.color='black';
    }

    function creditsLeave() {
        this.style.color='white';
    }

} // end of initialize

google.maps.event.addDomListener(window, 'load', initialize);
// Calls the function initialize after the document has loaded.
// This is the first thing to happen.
mhulse commented 5 years ago

There's also this one:

http://www.mapcrunch.com/js/main.min.js?v=6a

But IIRC, they use a defined set of lat/lons as starting locations. I think I had to use wayback machine to find the uniminified source of the JS; from there I was able to read the code. Etc.

mhulse commented 5 years ago

Liking the idea of using equirectangular map image, like:

https://visibleearth.nasa.gov/view.php?id=73963

Just created this:

map

Going to use a function from this repo:

https://github.com/ahalota/gridCoordinates

Next, need to read image and pick rando point and see if the pixel data is black; if white, pick another rando point.

This should save from API calls to Google!

mhulse commented 5 years ago

Random thoughts for docs later:

screenshot 2019-02-03 16 40 58

Using Photoshop, I open the info palette and can see pixel x/y.

Then using this bit of code:

getPixels("./test/map/map.png", function(err, pixels) {

  if (err) {
    console.log('Bad image path');
  } else {
    let width = pixels.shape[0];
    let height = pixels.shape[1];
    console.log('got pixels', pixels.get(1685, 832, 0))
  }

});

Pixel data at 1685 x and 832 y is: 255 white (only need one channel as this is black or white)

Pixel at 1686 and 832 is: 0 black.

So easy!

mhulse commented 5 years ago

Created:

https://github.com/mhulse/rand-land-coords

mhulse commented 5 years ago

Feature added to v2.0.0! It works!

Closing.