Last week I blogged on jQuery UI tabs and Google Maps. That led me to take another look at Yahoo's Maps API. I had worked with this sometime ago when I created my CFYahoo project. The code I wrote for the wrapper though only interfaced with their REST API. This let you get maps as images and save them to your server. This weekend I took a look at the AJAX API.
As you guess, this is a pure JavaScript solution. Include a script tag pointing to their service, create a map object, and you're good to go. In general, it worked well. I was a bit disappointed by their documentation though. Their reference guide spells out all the various methods, but is very slim on actual implementation details. So for example, you can create a YGeoPoint object from a latitude and longitude object, but they don't tell you what the valid values are in that range. I guess I learned that in Geography class 20 years ago, but my memory isn't what it used to be. Another example - they mention the various event handlers you can make use of, but from what I see, they don't specify what gets passed to the event. Nor do they fully explain what each event does. You can guess, of course, but I was surprised one more than one occasion when some code fired an event I didn't expect.
Yet another example (and I don't mean to harp so much on this, but it really bugs me when documentation is lacking, and yes, I know I've not done a great job at this myself) - there are 3 valid map types defined. They are: YAHOO_MAP_SAT, YAHOO_MAP_REG, YAHOO_MAP_HYB. Now I know that you can take a guess at these. Sat is for satellite, hybrid is satellite and regular, but would it have killed Yahoo to simply put a one line description of these fields somewhere in the docs?
Ok, I'll stop now. Once I figured things out and began to play, I decided to have some fun. Check out this template:
<html>
<head>
<script type="text/javascript" src="http://api.maps.yahoo.com/ajaxymap?v=3.8&appid=cfjedimaster"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<style type="text/css">
#map{
height: 100%;
width: 100%;
}
</style>
<script>
$(document).ready(function() {
// Create a map object
var map = new YMap(document.getElementById('map'));
var mapTypes = map.getMapTypes()
var validZooms = map.getZoomValidLevels()
//remove a few zooms since a lot of places won't let us zoom in
for(var i=0;i<5;i++) validZooms.shift()
//pop one or two off the top
for(var i=0;i<2;i++) validZooms.pop()
//no need for dragging
map.disableDragMap()
// Set map type to either of: YAHOO_MAP_SAT, YAHOO_MAP_HYB, YAHOO_MAP_REG
map.setMapType(YAHOO_MAP_REG);
// Display the map centered on a geocoded location
map.drawZoomAndCenter("San Francisco", 10);
window.setInterval(moveMe,6000)
function moveMe() {
var newLat = Math.floor(Math.random()*181)-90
var newLong = Math.floor(Math.random()*361)-180
var gp = new YGeoPoint(newLat,newLong)
map.panToLatLon(gp)
map.setMapType(getRandomType())
map.setZoomLevel(getRandomZoom())
}
function getRandomType() {
var type = mapTypes[Math.floor(Math.random()*mapTypes.length)]
return type
}
function getRandomZoom() {
var zoom = validZooms[Math.floor(Math.random()*validZooms.length)]
return zoom
}
})
</script>
</head>
<body>
<div id="map"></div>
</body>
</html>
This script creates a map object centered on San Francisco. (Sorry, I thought my home town was the center of the universe?) I then fire off an interval to run "moveMe" every 6 seconds. This function picks a random location on the planet, a random map type, and a random zoom level (although note I trim the zoom values at the edges). Basically what you get is a full screen view of the Earth at random locations.
You can see an example of this here (Note - most likely I will go over my usage limit for Yahoo, sorry!): http://www.coldfusionjedi.com/demos/yahoomapsajx/test2.html
Kind of fun, but tends to show ocean more often than not. No surprise there - we live on a planet that is 3/4ths water.
I then began work on a new version. I wanted a version where a map would fade in, instead of loading in blocks. I updated my code to use 2 DIVs. One for an initial map, another for the map that would load the new location. In theory, I could flip back and forth. I used the event handlers to fade out the old map, fade in the new map, and call out again in a few seconds to load a new map. In general it worked ok, but I removed the random zoom since zooming changed the map and forced another 'map loaded' event. I also removed the random map type. (Although you will see some of the code left over.)
<html>
<head>
<script type="text/javascript" src="http://api.maps.yahoo.com/ajaxymap?v=3.8&appid=cfjedimaster"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<style type="text/css">
.map {
height: 500px;
width: 500px;
float:left;
display:none;
}
#status {
float:left;
padding-left: 20px;
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
}
</style>
<script>
$(document).ready(function() {
//used for debugging
var TEMP = 0
//flag for m2
var m2flag = true
//size it, since map gets confused by hidden
size = new YSize(500,500)
// Create a map object for each div
var map1 = new YMap($("#map1").get(0),YAHOO_MAP_REG,size)
var map2 = new YMap($("#map2").get(0),YAHOO_MAP_REG,size)
//Listen for loading
YEvent.Capture(map1,EventsList.endMapDraw,mapLoaded)
YEvent.Capture(map2,EventsList.endMapDraw,mapLoaded)
//get data from map1, used for map types and zooms
var mapTypes = map1.getMapTypes()
var validZooms = map1.getZoomValidLevels()
//remove a few zooms since a lot of places won't let us zoom in
for(var i=0;i<5;i++) validZooms.shift()
//pop one or two off the top
for(var i=0;i<2;i++) validZooms.pop()
//no need for dragging
map1.disableDragMap()
map2.disableDragMap()
//begin with map1
var currentMap = "map1"
// Display the map centered on a geocoded location
map1.drawZoomAndCenter("San Francisco", 10);
function moveMe(thismap) {
console.log('moveMe '+thismap)
var newLat = Math.floor(Math.random()*181)-90
var newLong = Math.floor(Math.random()*361)-180
var gp = new YGeoPoint(newLat,newLong)
var mp = eval(thismap)
if(m2flag) {
mp.drawZoomAndCenter(gp, 10);
m2flag = false
} else {
mp.panToLatLon(gp)
//mp.setMapType(getRandomType())
}
$("#status").html("<p>Moved to "+newLat+" Lat, "+newLong+" Long</p>")
}
function getRandomType() {
var type = mapTypes[Math.floor(Math.random()*mapTypes.length)]
return type
}
function getRandomZoom() {
var zoom = validZooms[Math.floor(Math.random()*validZooms.length)]
return zoom
}
function mapLoaded(e) {
//console.log('loaded '+currentMap)
//fade out, fade in
if(currentMap == "map1") $("#map2").fadeOut('slow', function() { $("#map1").fadeIn('slow') })
else $("#map1").fadeOut('slow', function() { $("#map2").fadeIn('slow') })
//$("#" + currentMap).fadeIn('slow')
//call out to move next map
if(currentMap == "map1") {
nextMap = "map2"
currentMap = "map2"
} else {
currentMap = "map1"
}
/*
TEMP++
if(TEMP < 10) window.setTimeout(moveMe,5000,currentMap)
else console.log('ABORT')
*/
window.setTimeout(moveMe,5000,currentMap)
}
})
</script>
</head>
<body>
<div id="map1" class="map"></div>
<div id="map2" class="map"></div>
<div id="status"><p>Starting in San Francisco</p></div>
</body>
</html>
You can see this here: http://www.coldfusionjedi.com/demos/yahoomapsajx/test3.html
Like the first example, this one also spends a lot of time out at sea. I then decided to figure out the ranges for American long/lat. This took me a few minutes to get right. I ended up using the Polylines demo, which reported long/lat values for your mouse clicks, and used that to figure out ranges for the continental United States. (If anyone in Hawaii wants me to update this to support your state, please send plane tickets so that I may properly ensure my code is working correctly.) I modified moveMe function like so:
function moveMe(thismap) {
//console.log('moveMe '+thismap)
//Miami 25, Seattle 47
var newLat = Math.floor(Math.random()*22)+25
//Seattle 122, Maine 68
var newLong = Math.floor(Math.random()*57)+(-124)
var gp = new YGeoPoint(newLat,newLong)
var mp = eval(thismap)
if(m2flag) {
mp.drawZoomAndCenter(gp, 10);
m2flag = false
} else {
mp.panToLatLon(gp)
}
$("#status").html("<p>Moved to "+newLat+" Lat, "+newLong+" Long</p>")
}
The m2flag code simply handles initializing the second map correctly. I couldn't initialize it and not listen to it so I simply wrote a hack to handle the first time I want to set that map up. You can see this version here: http://www.coldfusionjedi.com/demos/yahoomapsajx/test4.html
As I said, useless, but kind of fun anyway.
Archived Comments
This is pretty cool. Maybe next week you can waste your time with that Flex in the browser window app that can receive push notifications - remember that? We were going to compare memory usage and you got busy trying to earn a living or something silly like that! ;)
Yeah Andy!! I do remember that ;)
Hey Ray,
check out http://www.openlayers.org/
it's a API for abstracting away all these company api's
so you can be provider independent
try and avoid commercial mapping API's where ever possible
What is wrong w/ a commercial API?
it's vendor lockin, by using openlayers you retain the ability to simply switch mapping providers.
in developing to say yahoo is you can't
just switch over to say VE, Google or openstreet maps, or even your own with something like MapGuide.
It's future proof, plus you can mix and match your mapping services
http://www.openlayers.org/d...
you might find this useful
http://ennoble.dreamhosters...
While I see your point, I'm not so sure needing to switch providers would be a common occurrence. I'd almost put it up there with switching database types. It _does_ happen, but is rare enough to end up being not something I worry about terribly, and in a properly MVCed application, it becomes even less of a worry. Just my two cents.
Ray, as someone who writes a lotta code, think about code reuse potential.
Jquery is for browsers, like Openlayers is for mapping.
If you have a client who live VE and and you prefer google, why redevelop/port the same stuff each time, when you can develop with openlayers?
I don't get the MVC reference as mapping is rather javascript heavy, porting that is still a pain.
The licensing terms for the commercial providers can be rather restrictive.
Of course it depends on what your doing, simple maps stuff aint such a big deal, but once you start working with them, it can get complex real quick
Well now wait a second. Code I write in jQuery is not going to port to Prototype or Spry. It is specific to the framework. As for the client example, if the client prefers VE, it doesn't matter if I prefer Google. I'll be using VE. :)
Again - I just don't see switching providers being something that would happen often.
Once again someone has found a way to take something that's perfectly fine and over-complicate it (seems to be the theme of the month). This is becoming all too common. Think about the events that would lead up to a switch...
1. The currently developed solution does not offer the right type of functionality and a new vendor would provide a better fit. (If this is the case then shame on the Project Managers and the Developer for not figuring this out before 1 line of code was written and doing it right the first time)
2. The solution is working fine and then for whatever reason the game is changed, maybe the web service provider tanked and went out of business. (If this happens it is just plain not your fault)
I used to think that I had to be "Object, Object, Object" and "Framework, Framework, Framework" all of the time and what I've come to realize is pretty simple and honest truth for me which is...
Rarely does anyone ever change database vendors on me and I've never gotten anything out of a framework other than a slower version of my original application.
Let me play Sean Corfield and say that this is "Right for Me - you do what you want but this works for Me".
What works for me is using DBX to quickly generate CRUD which I (perish the thought that you might have to actually get off your ass and write something on your own) copy and paste whatever portions of code I want into MY cfc's. I use hints and comments all over the place and I create external documentation as well and I typically plan apps around features and functionality rather than the data model.
@Zac - before you tell me that my code won't easily allow me to yank a solution from SQL Server and move it to ORACLE I will tell you that it's just not worth creating a slow app over and that the apps are documented well enough to allow for quick adjustments to the code to move from one environment to another.
Have at it everyone - tear me apart. While you're doing that and sifting through your hundreds of lines of auto-generated framework code to give you back a record I should have enough time to run to Chase and deposit a few checks, grab a vanilla latte and have a couple of cigarettes. Everyone is so worried about whether or not their app will run right on the Moon, Saturn, Jupiter and the Sun that I would wager that most of their apps never get done.
@Ray - please keep the examples coming. I've learned so much from your site and your books even though they were never cluttered with "and now do this in 20 other ways so that you can be prepared for a whole ton of crap that's not in the spec and will most likely never come to pass".
I believe that we don't need to see all 1000 ways to skin the cat, just 1 or 2 and we can probably figure out the rest for our particular "favorite personal way" of doing things.
I also believe that if someone is nice enough to blog an example and pass down knowledge for free that we probably shouldn't criticize it unless it just flat out doesn't work or is not factual.
Well, Andy, I'm not so sure Zac is trying to overly complicate things, nor do I think he is criticizing my approach even. I took his comments as his advice on it, nothing more, nothing less. :)
Damn't Ray I was trying to cause a fight and you have to come along and have a tone as calming as the HAL 9000.
Thanks for nothing ;)