timblack1 / rcl

Reformed Churches Locator
Other
2 stars 1 forks source link

find-a-church: Make proximity controls zoom the map #154

Open timblack1 opened 8 years ago

timblack1 commented 8 years ago

Plan A

Maaayyybe an easy solution would be to:

  1. Define a circle in these terms: it is centered on the map's center, and has the user's selected radius in miles/km.
  2. Find all congregations which fit within that circle. The Great Circle Formula would allow us to do this calculation, if we fed it the circle's center point and the congregation's location.
  3. Call Map.fitToMarkers() or set that property. That will zoom the map to the closest view which includes the matching congregations.

And you're done!

Note: One motivation for this method is that it appears Google's Maps API's zoom levels only exist in discrete integers, and can't actually be set in finer (fractional or decimal) increments.

Note: This implies when the user selects a search radius, we need to hide all the congregations which don't fit in that radius, and more importantly, when the user pans or zooms the map, we need to SHOW all the new congregations which fit within the map's new bounds, and stop using the circle as the bounds within which to display congregations. In other words, the map needs two modes here:

1) search radius/circle mode, and 2) panning-zooming/bounding box mode.

Plan B

Or, if we want to be all complicated like we used to think we had to, we could try any of the following:

It looks like the zoom levels don't correspond directly to miles. See http://gis.stackexchange.com/questions/7430/what-ratio-scales-do-google-maps-zoom-levels-correspond-to, http://stackoverflow.com/questions/6048975/google-maps-v3-how-to-calculate-the-zoom-level-for-a-given-bounds for information on how to translate from miles to zoom levels.

Some example algorithms:

http://jeffjason.com/2011/12/google-maps-radius-to-zoom/ http://stackoverflow.com/questions/5162950/google-maps-api-v3-set-zoom-level-to-show-a-given-radius

Note: Google Maps uses the "Google Web Mercator" projection and coordinate system. Its official EPSG identifier is EPSG:3857. It uses spherical rather than the standard Mercator ellipsoid formulae. According to https://en.wikipedia.org/wiki/Web_Mercator#Properties, "While the Web Mercator's formulas are for the spherical form of the Mercator, geographical coordinates are required to be in the WGS 84 ellipsoidal datum." Formulae are available at https://en.wikipedia.org/wiki/Web_Mercator#Formulas. (Question: Do x & y values there refer to the bounding box's height & width in pixels?) Note there are a couple more formulae in a JavaScript-like language at the end of this article: https://alastaira.wordpress.com/2011/01/23/the-google-maps-bing-maps-spherical-mercator-projection/.

See general Google Maps coordinate overview at https://developers.google.com/maps/documentation/javascript/maptypes?csw=1#MapCoordinates.

This might work:

When a user changes the search radius (in miles/km):

  1. Find the distance between the current viewport's bounding box's center and a point on the box's edge directly to the left or right of that center point. This is the search radius currently displayed on the map.
    • Use the map.getBounds() function to get the latitude and longitude of the map's corners.
    • To find the left or right point, see if Google Maps provides an API. If not, then find the point halfway between the top and bottom corners of the box on that side. What is tricky here is to know what units to use to define that halfway point: degrees, radians, or miles.
    • We can use a Great Circle Distance formula to calculate the distance in miles between the two points. We might also be able to use https://developers.google.com/maps/documentation/javascript/geometry computeDistanceBetween() to do the same.
  2. Get the map's current zoom level from the Google Maps API.
  3. Use a Google Web Mercator (perhaps identical to a formula for Universal Transverse Mercator) mileage conversion formula to calculate a new zoom level for the map.

Here is a Great Circle Distance formula: http://stackoverflow.com/questions/3525670/radius-of-viewable-region-in-google-maps-v3.

timblack1 commented 8 years ago

Doug, I'm going to leave my notes here for you so you can work on the algebraic refactoring when you have time.

We start with this:

ratio_milesperzoomlevel = dis/zoom

Our goal: get new zoom level

So it seems this would be the right formula:

zoom * ratio_milesperzoomlevel = zoom * dis/zoom zoom = dis/ratio_milesperzoomlevel

But that doesn't work, because according to docs describing Google Maps, when the zoom goes up linearly, miles go down quadratically, as is represented in the following table.

zoom miles divide by
0 24000 1
1 12000 2
2 6000 4
3 3000 8
4 1500 16
5 750
6 375
7 165

So the above table can be represented by the following formula. How can we factor out z to be the only term on the left side of the equation?

new_radius_miles = original_radius_miles/(2^z)
(2^z)*new_radius_miles = original_radius_miles
(2^z) = original_radius_miles/new_radius_miles

TODO: Start here. How can we factor out z?

z = ?

The following might work:

(2^new_zoom) = original_radius_miles/new_radius_miles

We can factor out new_zoom using the following formula: For a = b^c, c = log b of a, or c = Math.log(a) / Math.log(b)

var new_zoom = Math.log(dis/Number(this.search_radius)) / Math.LN2;
timblack1 commented 8 years ago

@DouglasHuston, see http://math.stackexchange.com/questions/956776/whats-the-inverse-operation-of-exponents

timblack1 commented 8 years ago

Try:

(2^z) = original_radius_miles/new_radius_miles
// For a = b^c, c = log b of a, or c = Math.log(a) / Math.log(b)
z = Math.log10(original_radius_miles/new_radius_miles) / Math.log10(2)
timblack1 commented 8 years ago

This is the order in which the user's interaction works. First, the interface has a zoom level. Then the user selects a new search radius (in miles), which moves the user's selection up or down the rows of the table below. We need to calculate the new zoom level which results.

miles zoom divide original miles by (shows quadratic progression)
24000 0 1
12000 1 2
6000 2 4
3000 3 8
1500 4 16
750 5 32
375 6 64
187.5 7 128

When miles go down by 1/2, zoom goes up by 1. When miles go up by 2x, zoom goes down by 1. We need to translate this into a function which takes in a new number of miles selected by the user, and outputs a new zoom level.

newMiles = oldMiles/(2^(newZoom - oldZoom)) (2^(newZoom - oldZoom)) * newMiles = oldMiles 2^(newZoom - oldZoom) = oldMiles/newMiles 2^(newZoom - oldZoom) = oldMiles/newMiles log_2(oldMiles/newMiles) = newZoom - oldZoom log_2(oldMiles/newMiles) + oldZoom = newZoom

Check out the Intro to Logarithms at https://www.khanacademy.org/math/algebra-home/alg-exp-and-log.

timblack1 commented 8 years ago

We've implemented this partially, and paused development until we hear back from https://github.com/GoogleWebComponents/google-map/issues/339 as to whether we can use non-integer zoom values. For now we just round search radii to the nearest integer zoom level, which is not as fine a resolution as we would like. We did this so we could merge the feature branch and not let it get far behind the develop branch. We hope to come back to this later.

timblack1 commented 7 years ago

Problem: Our current algorithm for calculating the zoom level seems to be correct, so long as the map's pixel dimensions remain the same. If the user resizes the window, making the map change pixel dimensions, then the calculations are not accurate anymore.

This raises the question, is the map's initial zoom level correctly coordinated with the initial search radius entered in the search radius input? If so, how can we be sure it will be correct on different screen sizes? If not, then the algorithm may be incorrect.

timblack1 commented 7 years ago

Here's a recommendation for how to get the right algorithm:

  1. Find out which map projection Google Maps uses. For example, the "Universal Transverse Mercator" projection.
  2. Find a standard equation for that projection which will permit us to calculate what we need. The equation might be called a "mileage conversion formula."
DouglasHuston commented 7 years ago

http://stackoverflow.com/questions/2656580/common-mercator-projection-formulas-for-google-maps-not-working-correctly

If the viewing area is defined in Lat/Lng format, The distortion happens when it is populated with the appropriate Mercator projections. Instead, the correct viewing box must be defined in Mercator or Project Mercator.

timblack1 commented 7 years ago

@DouglasHuston, is the point of your last comment on here (at https://github.com/timblack1/rcl/issues/154#issuecomment-280792480) that we need to use the Mercator projection?

DouglasHuston commented 7 years ago

If we do use the Mercator projection, we must define the viewing box correctly, and not just use the Lat/Lng format. This does not mean we are restricted to only using the Mercator projection.