tag:blogger.com,1999:blog-84389815254166345222024-02-20T12:28:04.603-08:00Maps hacks and Rock`n`RollDont afraid maps. Google maps v3, v2, ovi, yandex maps.
Maps is your friend.
Try them, love them.
I`ll show how to cook themKasheyhttp://www.blogger.com/profile/06277598811675041059noreply@blogger.comBlogger6125tag:blogger.com,1999:blog-8438981525416634522.post-27025370602779203862010-03-23T04:46:00.001-07:002010-03-23T04:46:19.014-07:00Back to basics - lets start the map<div> <div style="border: 2px dotted gray;"> <p><b>Mission:</b>Start map</p> <p><b>Goal:</b>Reduce fails</p> </div> <h2>Step1. Test yourself</h2> In the beggining - test your own map application.<br><br />
Do this in two steps:<br><br />
Impliment some king of "loading guard", it will fires if map wont start in 40 seconds<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br> setTimeout(function(){<br />
<br> somehow test what map is ready and workable.<br />
<br> for example - map will fires event "weAreDone".<br />
<br> catch it!<br />
<br> },40000);<br />
<br> /* i thing 40sec is alot of time*/<br />
<br></span><br />
</code></code></blockquote>and then define global error catcher<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br>function global_error_catcher(msg, url, num, more){<br />
<br> if(msg && msg.name){<br />
<br> var _event=msg;<br />
<br> msg=(_event.name+"] "+e.fileName+":"+e.lineNumber+"\n"+e+"\n"+e.stack);<br />
<br> }<br />
<br> if(typeof(msg)=='object'){<br />
<br> var evt=msg;<br />
<br> msg='';<br />
<br> for(i in evt)msg+=i+":"+evt[i]+"\n";<br />
<br> }<br />
<br> var textout=("global error:\n"+msg+",\n"+url+":"+num+"\n:"+more);<br />
<br> new Ajax.Request("loader/report_site_error.php",{parameters:{'message':textout},async:true});<br />
<br> return true;<br />
<br> }<br />
<br> window.onerror=global_error_catcher;<br />
<br></span><br />
</code></code></blockquote>Then put this application <b>online</b> and see logs.<br><br />
So i say "<u><strong>if map working for you - it can not work for others</strong></u>"<br><br />
And <u>this is true</u>.<br><br />
<br><br />
some time ago i got bug report "hey men, there is no map on your site".<br><br />
I test it - map exists, i test my site on other PC.. map exists..<br><br />
I try to ask friends to test site... and.. MAP EXISTS!!!<br><br />
but next day i`v got same bug report from other user..<br><br />
So i impliment listed debug scripts and then i see..<br />
<b>2000 users per day</b> see my site with out map.<br />
<b>2000 users of 50k</b> - 4%<br><br />
So how to fix it <br />
<h2>Step2. Start map</h2> First include google loader<br><br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br>function add_script(src){<br />
<br> var fileref=document.createElement("script");<br />
<br> fileref.type="text/javascript";<br />
<br> fileref.src=src;<br />
<br> document.getElementsByTagName("head")[0].appendChild(fileref);<br />
<br>}<br />
<br><br />
<br>function _do_requestLoader(domain){<br />
<br> add_script("http://www.google."+domain+"/jsapi?hl=ru&key="+GM_APIKEY);<br />
<br>}<br />
<br><br />
<br>var requestLoader_try=0;<br />
<br>function requestLoader(){<br />
<br> if(requestLoader_try==0)_do_requestLoader("your domain code(.us,.ru,not just .com)");<br />
<br> else _do_requestLoader("com");<br />
<br> requestLoader_try++;<br />
<br> <br />
<br> map.__init_loadingCycle(0);<br />
<br>}<br />
<br></span><br />
</code></code></blockquote>Next try to call it and include google maps<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br>GoogleMap.prototype.__init_loadingCycle = function (tick){<br />
<br> clearTimeout(this.__loadingTm); <br />
<br> if(tick++>200){<br />
<br> requestLoader();<br />
<br> return;<br />
<br> }<br />
<br> var thisworker=this;<br />
<br> //we have google loaded. sometimes( apple\safari google loads in 2-5 tick! )<br />
<br> if(window.google && typeof(window.google)!=undefined && typeof(window.google.load)=='function'){<br />
<br> this.console("google start at "+tick+" tick");<br />
<br> thisworker._enterState('GMaps.load',function(){<br />
<br> google.load("maps", "3", {other_params:"sensor=true",<br />
<br> "callback" : function(){<br />
<br> if(thisworker.isClosed){<br />
<br> thisworker.console("google post-activaion");<br />
<br> return;<br />
<br> }<br />
<br> thisworker._enterState("google start",function(){<br />
<br> thisworker.start();<br />
<br> },0);<br />
<br> }, <br />
<br> "sensor":"true",<br />
<br> //and some others params like<br />
<br> "language" : "ru",<br />
<br> "hl":"ru",<br />
<br> "base_domain":"maps.google.ru"<br />
<br> });<br />
<br> },0);<br />
<br> }<br />
<br> else{<br />
<br> //start timer<br />
<br> this.__loadingTm=setTimeout(function(){thisworker.__init_loadingCycle(tick)},10);<br />
<br> }<br />
<br>}<br />
<br></span><br />
</code></code></blockquote>Next wait until maps starts, and request maps projection<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br>//lest define projection helper<br />
<br>function GoogleV3ProjectionHelperOverlay(map)<br />
<br>{<br />
<br> this.setMap(map);<br />
<br>}<br />
<br><br />
<br>GoogleMap.prototype.start = function ()<br />
<br>{<br />
<br> var _this=this;<br />
<br> //leave loading state<br />
<br> this._leaveState(this.loadingState);<br />
<br> var myOptions = {<br />
<br> zoom: this.driver.getZoom(),<br />
<br> center: this.fromLatLng(this.driver.getPosition()),<br />
<br> mapTypeId: this.decodeMapType(this.driver.getMapType()),<br />
<br> disableDefaultUI:true,<br />
<br> scrollwheel:true,<br />
<br> keyboardShortcuts:false <br />
<br> }<br />
<br> var thisworker=this; <br />
<br> this.api = new google.maps.Map(this.divId,myOptions); <br />
<br> this.helperOverlay=0;<br />
<br> this.startIniter=0;<br />
<br> <br />
<br> //then define help projector<br />
<br> GoogleV3ProjectionHelperOverlay.prototype = new google.maps.OverlayView();<br />
<br> GoogleV3ProjectionHelperOverlay.prototype.draw = function () {<br />
<br> if (!this.ready) {<br />
<br> google.maps.event.trigger(this, 'ready'); <br />
<br> this.ready = true;<br />
<br> <br />
<br> //if this overlay ready - start maps<br />
<br> this.tearMapOn(0);<br />
<br> }<br />
<br> };<br />
<br> <br />
<br> GoogleV3ProjectionHelperOverlay.prototype.tearMapOn = function(tick){<br />
<br> //if it have panes - start map<br />
<br> if(this.getPanes()){<br />
<br> _this._enterState("google-doStartInit - "+tick,function(){ <br />
<br> _this.doStartInit();<br />
<br> });<br />
<br> }<br />
<br> else{<br />
<br> var __this=this;<br />
<br> setTimeout(function(){__this.tearMapOn(tick++)},1);<br />
<br> }<br />
<br> }<br />
<br> <br />
<br> //sometimes it good to know how much time took loading of all tiles of map<br />
<br> this.idleTimer=google.maps.event.addListener(this.api,'idle',function(){<br />
<br> _this.weAreDone();<br />
<br> google.maps.event.removeListener(_this.idleTimer); <br />
<br> });<br />
<br> <br />
<br> this.api.setCenter(myOptions.center);<br />
<br> this.api.setZoom(myOptions.zoom);<br />
<br> this.api.setMapTypeId(myOptions.mapTypeId);<br />
<br> <br />
<br> //call some callback on API init<br />
<br> this.onReadyAPI('google','init',this);<br />
<br> <br />
<br> //init helperOverlay<br />
<br> this.helperOverlay=new GoogleV3ProjectionHelperOverlay(this.api);<br />
<br> this.helperOverlay.setMap(this.api);<br />
<br> this.helperOverlay.draw();<br />
<br>}<br />
<br><br />
<br>//this function will be called this helperOverlay starts<br />
<br>GoogleMap.prototype.doStartInit = function ()<br />
<br>{<br />
<br> if( this.startIniter )<br />
<br> google.maps.event.removeListener(this.startIniter); <br />
<br> this.onMapReady();<br />
<br>}<br />
<br><br />
<br></span><br />
</code></code></blockquote>Map started!<br><br />
and after this step we can convert latlng to pixels as in v2.<br />
Many users want fromLatLngToContainerPixel - here it is!<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br>GoogleMap.prototype.modPointByOffset=function(point,fac){<br />
<br> var bounds=this.api.getBounds();//this.getBounds();<br />
<br> var swPoint = this.fromLatLngToDivPixel(bounds.getSouthWest());<br />
<br> var nePoint = this.fromLatLngToDivPixel(bounds.getNorthEast());<br />
<br> return {x:point.x-swPoint.x*fac,y:point.y-nePoint.y*fac};<br />
<br>}<br />
<br> <br />
<br>GoogleMap.prototype.fromLatLngToContainerPixel=function(a){<br />
<br> var point=this.fromLatLngToDivPixel(a);<br />
<br> return this.modPointByOffset(point,1);<br />
<br>}<br />
<br>GoogleMap.prototype.fromContainerPixelToLatLng=function(a){<br />
<br> var point=this.modPointByOffset(a,-1);<br />
<br> return this.fromDivPixelToLatLng(point);<br />
<br>}<br />
<br>GoogleMap.prototype.fromLatLngToDivPixel =function(a){<br />
<br> if(!this.helperOverlay) return {x:0,y:0};<br />
<br> return this.helperOverlay.getProjection().fromLatLngToDivPixel(a);<br />
<br><br />
<br>}<br />
<br>GoogleMap.prototype.fromDivPixelToLatLng =function(a){<br />
<br> return this.toLatLng(this.helperOverlay.getProjection().fromDivPixelToLatLng(a));<br />
<br>}<br />
<br></span><br />
</code></code></blockquote><h2>Step3. Test it again</h2> So we use enter and leavestate functions. The log working flow. If some event "enters", but not "leaves" - we will log.<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br>GoogleMap.prototype._leaveState = function(_event){<br />
<br> var _this=this;<br />
<br> _event.leave=_this.microtime();<br />
<br> _this.console("["+_event.name+"] took " +(_event.leave-_event.enter));<br />
<br> return _event;<br />
<br>}<br />
<br><br />
<br>GoogleMap.prototype.__callState = function (_event){<br />
<br> try<br />
<br> {<br />
<br> _event.func();<br />
<br> this._leaveState(_event) ;<br />
<br> }catch(e){<br />
<br> _event.error=e;<br />
<br> this.console("state-error ["+_event.name+"] "+e.fileName+":"+e.lineNumber+"\n"+e+"\n"+e.stack);<br />
<br> this._leaveState(_event) ;<br />
<br> this.__stateException(_event,e);<br />
<br> ;<br />
<br> }<br />
<br>}<br />
<br><br />
<br>GoogleMap.prototype._enterState = function(name,func,sync,catcheble){<br />
<br> var _event={'name':name,'func':func,'enter':this.microtime(),'leave':0,'catcheble':catcheble};<br />
<br> var _this=this;<br />
<br> this.console('entering ['+name+']'+this._stages.length);<br />
<br> this._stages.push(_event);<br />
<br> if(func){<br />
<br> clearTimeout(this._stageTimeout);<br />
<br> if(sync){ _this.__callState(_event);<br />
<br> }<br />
<br> else{<br />
<br> this._stageTimeout=setTimeout(<br />
<br> function(){<br />
<br> _this.__callState(_event);<br />
<br> },1);<br />
<br> }<br />
<br> }<br />
<br> return _event;<br />
<br>}<br />
<br><br />
<br></span><br />
</code></code></blockquote><br><br />
See error logs again.<br />
<h3>W\O this patches 1-5% of request fails</h3> with this patches still some request fails, but just some of this.<br />
<br><br><br />
so test yourself and see. I test this on 4 sites with 150k unique visitors per day. And i think it is true that map exists not for all of then then you use "standart" way<br><br />
What about you?<br />
<br />
<br><br><br />
<div style="border: 2px dotted gray;"> All of sections, i list here, is allready done by me. I need just to wrap code into article, and, yap, english article :)<br><br />
If you want to know more - <a href="mailto://thekashey@gmail.com">ask</a><br />
</div><br><center><h1 style="font-size: 12px; padding: 4px; background-color: rgb(192, 96, 0); margin: 0pt;">How to start Google V3</h1></center></div>Kasheyhttp://www.blogger.com/profile/06277598811675041059noreply@blogger.com0tag:blogger.com,1999:blog-8438981525416634522.post-28478726886645175942010-03-19T02:25:00.001-07:002010-03-19T02:25:43.405-07:00Place 10000 markers on map. And cluster them<div align="left" style="width: 95%; height: 95%;" id="icontent"> <div> <div style="border: 2px dotted gray;"> <p><b>Mission:</b>Make map workable with 1000 markers on it</p> <p><b>Goal:</b>Cluster and filter</p> </div> note: if you use google markers directly - you will loose. Rule the MVC and split marker into data,view and controller.<br />
Fetch data from server and store it as data, create controller objects basing on this data.<br />
Next controller will create view, and then view can create marker( or inherits from GOverlay, or just return GIcon and others ballons and hints for marker).<br />
<br><br />
In this article we work with data<br />
<br><hr><br><br><br />
<h2>Step1. Create 1000 markers </h2> <blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br>somehow<br />
<br></span><br />
</code></code></blockquote><h2>Step 2 or 3. Filter visible markers by viewport</h2> We dont see markers behind map borders. Why not to hide them?<br />
<h2>Step 3 or 2. Some markers instersects. Glue them</h2> Is one marker hide other marker - why not to hide bottom marker. Sometimes we dont see it.<br><br />
Sometimes we see it, but we can click it. And sometimes maps become just a bit <strong>overloaded</strong><br />
<br><img src="http://habrahabr.ru/pictures/00/00/01/21/77/picture_10.jpg"><br><br />
<br><br />
<h3>Lets solve</h3> So we sort markers by some field( actuality, qualyty or roughneck )<br><br />
Next we take one marker, determine area he "own" and moving all markers in this area from map into "stackedChilds" field of first marker.<br><br />
<br><img src="http://habrahabr.ru/pictures/00/00/01/21/77/picture_11.jpg"><br><br />
Next take next, still alive marker, and go on.<br><br />
To speed up this step <strong>lets use "grid" hashtable</strong>.<br><br />
Divide area into WxH rect pieces( one rect must be smaller than marker ).<br><br />
Precalculate integer indexes of markers position in this grid, and next search you neightboards only in this area<br />
<h2>So what step goest first?</h2> <ul> <li>Clustering is a bit complex thing. I takes a lot of time, so lets filter markers first.</li>
<li>Dammm it! Now then i move map new markers appers and after clustering i`v gov new image( cos we have new set of data )</li>
</ul> Lets work around this.<br />
<h2>Map blocks, Cluster index and render set</h2> <p>As told you - we load markers from server in set of tiles. So then you filter - filter only markers from data tiles you can see now.</p> <i>blocks... tiles... indexes...</i><br><br />
<h3>Lets repeat this technics one more time</h3> Lets divide map to several blocks( and lets name them clusters ), and cluster them as atoms. <br><br />
cluster step 1: determine visible set<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br> //setup cluster by current bounds, zoom and generation<br />
<br> //hint - generations incriments on each marker insert, or block insert.( so one every change of markers set )<br />
<br> var workingset=this.cluster.setup(bounds.x1,bounds.y1,bounds.x2,bounds.y2,bounds.zoom,ingen);<br />
<br> var realbounds=this.cluster.getBounds();<br />
<br> <br />
<br> //we have update!<br />
<br> if(workingset.update.length){<br />
<br> this.cluster.start(workingset);<br />
<br> //this function will insert markers to cluster<br />
<br> QRprepare();<br />
<br> this.cluster.end();<br />
<br> }<br />
<br></span><br />
</code></code></blockquote>this functions returns set of cluster to update and bounds cluster want to calculate<br><br />
next insert data into<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br>if(((obj.LATITUDE)>realbounds.y1 && (obj.LATITUDE)<realbounds.y2 &&<br />
<br> (obj.LONGITUDE+dimw)>realbounds.x1 && (obj.LONGITUDE-dimw)<realbounds.x2)){<br />
<br> this.cluster.push(obj);<br />
<br>}<br />
<br></span><br />
</code></code></blockquote>next ask cluster about visible markers<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br>//ask cluster to calculate visible set<br />
<br>this.cluster.setVisibleSet();<br />
<br>//visible objects, hide all not in this set<br />
<br>var torender=this.cluster.getRender(tilethis.LIMIT_OBJECTS);<br />
<br>//hidden objects, show all in this set<br />
<br>var updateset=this.cluster.getUpdateset(tilethis.LIMIT_OBJECTS);<br />
<br></span><br />
</code></code></blockquote><strong>render set</strong> is a VISIBLE markers, and you need to hide all markers not in this set( by yourself )<br><br />
<strong>update set</strong> is a set of markers you must show, or update( update also fires then changes internal <i>stack-count</i> of marker)<br><br />
So hide some markers and show others<br />
Note: we collect all hide\show\update in long arrays and execute them in packs. DIP cost and so so :)<br><br />
To do same just HIDE map, show\hide 10-100 markers and SHOW map.<br><br />
if you just showing markers - you can create them in other node, detached from document, and then you ready - attach this "group marker" to map node<br><br />
There is also mistical "DocumentFragments", margical reflow-and-redraw and other dreadfull things, we dont talk about.<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br>//hide<br />
<br>for(i in this.visiblePipeline){<br />
<br> if(i && this.visiblePipeline[i])<br />
<br> if(!torender[i] || typeof(torender[i])=='undefined'){<br />
<br> this.hideObject(i);<br />
<br> }<br />
<br>}<br />
<br>//and show<br />
<br>for(ii in updateset){<br />
<br> i=updateset[ii];<br />
<br> tilethis.showObject(i);<br />
<br>}<br />
<br>//hint: show commands will be stored in visiblePipeline<br />
<br></span><br />
</code></code></blockquote><h2>End. So what we got</h2> 1. First ask cluster for bounds it want to calculate<br><br />
2. Put data only in nodes cluster want to be updated.<br><br />
3. Clustering is executed only then you have some update or maps moves more than cluster-size pixels<br><br />
4. Cluster rule you! It produce dirrect comands to show\hide, and you just execute.<br><br />
5. My english is not so good as this article. Please turn on your own brain-spell-corrector and correct me. <br />
<br><i> (<strong>comrade</strong> - to much vodka i drink, and bears cry out on the street and i can sleep ) </i><br />
to find bears on the streets of moskow and <strong>see how this clustering works</strong> - try <a href="http://www.gdeetotdom.ru/map/#lat=55.75519&lng=37.56276&m=google&z=13&l=7-8-14-42">GdeEtotDom</a><br />
<h2>Bugs?</h2> In this implemetation there is one feature, not the bug.<br><br />
As we calcucate cluster as atoms - one cluster dont know nothing about markers in next cluster.<br><br />
So one cluster truly thinks that markers near the edge dont have niegboards( the pass to other cluster )<br><br />
So you can got something like this.<br><br />
<img src="http://www.kashey.ru/img/maps/overlap.png"><br><br />
How to reduce this effect:<br><br />
variant 1 - make cluster bigger<br><br />
variant 2 - after clustering tiles we got static set of <strong>rendermarkers</strong>. Lets try apply one more cluster index on this set, but move cluster start by CLUSTERSIZE/2 px.<br><br />
As result new clusters will glue wrong markers.<br><br />
<br />
<h1>Try it by yourself</h1> For now you can <a href="http://www.kashey.ru/maps/api/cluster.js">try this version</a>, but file is not documented and also you can see how it works <a href="http://www.gdeetotdom.ru/map/#lat=55.75519&lng=37.56276&m=google&z=13&l=7-8-14-42">here</a><br />
<br><br><br />
<i>some images used from <a href="http://habrahabr.ru/blogs/google/28621/">this</a> article</i><br />
<div style="border: 2px dotted gray;"> <b>Homework:</b> And what it R-trees? <br />
</div></div><br><br><br />
<div style="border: 2px dotted gray;"> All of sections, i list here, is allready done by me. I need just to wrap code into article, and, yap, english article :)<br><br />
If you want to know more - <a href="mailto://thekashey@gmail.com">ask</a><br />
</div><br><center><h1 style="font-size: 12px; padding: 4px; background-color: rgb(192, 96, 0); margin: 0pt;">How to cluster map markers in serven days</h1></center></div><br />
this is a mirror of <a href="http://kashey.ru/maps/clustering-map-markers/">this article</a>Kasheyhttp://www.blogger.com/profile/06277598811675041059noreply@blogger.com1tag:blogger.com,1999:blog-8438981525416634522.post-44666538399223675882010-03-17T01:44:00.000-07:002010-03-17T02:45:16.062-07:00I`ll carve you into tiles<div align="left" style="width: 95%; height: 95%;" id="icontent"><div><div style="border: 2px dotted gray;"><p><b>Mission:</b>Make loading a bit faster, and a bit cleaner</p><p><b>Goal:</b>Load data in set of information tiles</p></div>loading data blocks via custom map type<br />
<h2>Step 1. Load data into map</h2>Lets request server for tiles in visible viewport<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br />
$.get("ajax/load-map-data?SE="+MySECorner.toString()+"&NE="+MyNECorner.toString()+"&zoom"+currentZoom,<br />
<br />
function(data){<br />
<br />
//wow! i got the data for map!<br />
<br />
//forwhole visible viewport( and just for it)<br />
<br />
});<br />
<br />
</span><br />
</code></code></blockquote><h2>Step 2. Kill users, server and youself</h2>Just answer two single things<br />
<ul><li>will you rerequest data if maps is moved by pixel</li>
<li>can you cache request</li>
</ul>If first answer is "no" and second is "no" - close this page. You allready find nirvana :)<br />
<br />
And if you still here - you loading your data in wrong way, yet. Until now.<br />
<h2>Step 3. How google loads image for tiles?</h2>He(it?) loads it in.. tiles, segments 256x256 pixels :)<br />
<h2>Step 4. Think! Why not to do same?</h2>If you load you marker data in set of tiles - <br />
<ul><li>you can <b>Perfectly</b> cache them</li>
<li>Map shift below 256px will not produce any requests to server</li>
<li>Normal browser can perform 4-6 request to one server at one moment, so you`ll ask tiles in parrallel, 4-6 times faster</li>
</ul>Define your own tile source, not for image tiles, but for data tiles<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br />
<br />
<br />
var ServerFetchedTiles=[];<br />
<br />
<br />
<br />
function ServerFetchMapType() {<br />
<br />
}<br />
<br />
<br />
<br />
ServerFetchMapType.prototype.tileSize = new google.maps.Size(256,256);<br />
<br />
ServerFetchMapType.prototype.maxZoom = 32;<br />
<br />
ServerFetchMapType.prototype.name = "Server Tile #s";<br />
<br />
ServerFetchMapType.prototype.alt = "Server Data Tile Map Type"; <br />
<br />
<br />
<br />
ServerFetchMapType.prototype.getTile = function(coord, zoom, ownerDocument) {<br />
<br />
var addr=this.getAddr(coord,zoom);<br />
<br />
//test - may be we allready load this block?<br />
<br />
if(ServerFetchedTiles[addr]){<br />
<br />
return<br />
<br />
}<br />
<br />
$.get("ajax/load-map-tile?"+addr,<br />
<br />
function(data){<br />
<br />
ServerFetchedTiles[addr]=data;<br />
<br />
//wow! i got the data just for this tile<br />
<br />
//and any map moving is nothing for me!<br />
<br />
});<br />
<br />
};<br />
<br />
<br />
<br />
//and add this tile overlay to current map type<br />
<br />
map.overlayMapTypes.insertAt(0, new ServerFetchMapType() );<br />
<br />
<br />
<br />
</span><br />
</code></code></blockquote>And you can request tiles in two ways - as google do (x,y,z), or as latlngbox.<br />
I write some code for second variant( i think you`ll use second at the begining )<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br />
//we can request tile as tile(x,y,z)<br />
<br />
ServerFetchMapType.prototype.getAddrXY = function(coord, zoom){<br />
<br />
return "x="+coord.x+"&y="+coor.y+"&z="+zoom;<br />
<br />
}<br />
<br />
<br />
<br />
//or as latlng box<br />
<br />
ServerFetchMapType.prototype.getAddrLatLng = function(coord, zoom){<br />
<br />
<br />
<br />
//helper function for mercator projection<br />
<br />
var mercator = function(point){<br />
<br />
//<br />
<br />
var numtiles=Math.pow(2,point.zoom);<br />
<br />
var bitmapsize=numtiles*256;<br />
<br />
var bitmaporigo =bitmapsize/2;<br />
<br />
var pixelsperlondegree=bitmapsize/360;<br />
<br />
var pixelsperlonradian=bitmapsize/(2*Math.PI);<br />
<br />
<br />
<br />
var lat=bitmaporigo+(pixelsperlondegree*point.x);<br />
<br />
var ged2rad=((point.t*Math.PI)/180.0);<br />
<br />
var sn=Math.sin(deg2rad);<br />
<br />
var lng=((bitmaporigo - 0.5 * log((1+sn)/(1-sn)) * pixelsperlonradian));<br />
<br />
return new google.maps.LatLng(lat,lng);<br />
<br />
}<br />
<br />
var point1={x:coord.x*256,y:coord.y*256,z:zoom} ;<br />
<br />
var point2={x:(coord.x+1)*256,y:(coord.y+1)*256,z:zoom} ;<br />
<br />
var ne=mercator(point1);<br />
<br />
var sw=mercator(point2);<br />
<br />
return "NE="+ne.toString()+"&SW="+sw.toString()+"&z="+zoom;<br />
<br />
}<br />
<br />
</span><br />
</code></code></blockquote>more info about map tiles can be found at <a href="http://code.google.com/intl/ru/apis/maps/documentation/v3/overlays.html#CustomMapTypes">google code</a>, and there they point this idea somehow.<br />
<i>Idea - you can load not only images</i><br />
<h2>Step 5. Enjoy</h2>Now try to impliment pieces of this into your own code. Or see how it works on<br />
<ul><li><a href="http://wikimapia.org">Wikimapia</a></li>
<li><a href="http://www.esosedi.ru">eSosedi.ru</a></li>
<li><a href="http://www.gdeetotdom.ru/map/">GdeEtotDom.ru</a></li>
</ul><h2>What lasts?</h2>Ask one question - what you will do if there is no data for tile( you are viewing siberia ), may be just load tile for other zoom( currrent_zoom-2? ).<br />
And how to do it?(<b>we do it</b>).<br />
<br />
<br />
<br />
<div style="border: 2px dotted gray;"><b>Homework:</b> and why cites, listed above use other technics? Benefits of quadtree?<br />
<i>( ps: in this mode tiles can he cached on file level. For dirrect nginx access )</i><br />
</div></div><br />
<br />
<br />
<div style="border: 2px dotted gray;">All of sections, i list here, is allready done by me. I need just to wrap code into article, and, yap, english article :)<br />
<br />
If you want to know more - <a href="mailto://thekashey@gmail.com">ask</a><br />
</div><br />
<center><h1 style="font-size: 12px; padding: 4px; background-color: rgb(192, 96, 0); margin: 0pt;">Load data into google maps. And do it as google do</h1></center></div>Kasheyhttp://www.blogger.com/profile/06277598811675041059noreply@blogger.com1tag:blogger.com,1999:blog-8438981525416634522.post-5712725762939914812010-03-16T06:13:00.000-07:002010-03-16T06:13:34.719-07:00Codename: Romeo-Zulu-Nine-Quebec-Whisky<a href="http://romeowhiskey.net/">http://romeowhiskey.net/</a><br />
<img src='http://romeowhiskey.net/images/blogger.jpg'><br />
yes, this is dash board on google maps v3.<br />
<br />
(tech version )Kasheyhttp://www.blogger.com/profile/06277598811675041059noreply@blogger.com0tag:blogger.com,1999:blog-8438981525416634522.post-60767987952506553622010-03-16T06:02:00.001-07:002010-03-16T06:02:56.283-07:00Basic polygon editor<div> <div style="border: 2px dotted gray;"> <p><b>Mission:</b>Make interface able to edit polygon</p> <p><b>Goal:</b>Create polygon with edit interface</p> </div> <h2>Step1. Create Polygon</h2> Lets define polygon of 3 points<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br> var verticles=[new GLatLng(37.4419 , -122.1419),<br />
<br> new GLatLng(37.4419+0.03, -122.1419),<br />
<br> new GLatLng(37.4419 , -122.1419+0.03)]<br />
<br> <br />
<br> myPoly = new myEditablePolygon(verticles,map);<br />
<br> myPoly.attachClickEvent();<br />
<br> myPoly.draw();<br />
<br> </span><br />
</code></code></blockquote>What next?<br><br />
Next - we need to edit it. Drag edge markers, create new, and delete unused<br><br />
And i`ll present <b>THREE</b> variants of editor. In one box :)<br><br />
<br><br />
bla-bla-bla.. lets go down to mathematics<br />
<h2>Step2. Determine where we click, and place to add new point</h2> Algorithm is the same as in article about <a href="http://www.kashey.ru/maps/howto-reduce-polygon/">polygon reduce</a><br><br />
Take the point, find distance from it to all segments of polygon and add new verticle in right place!<br><br />
So tranform it to screen space<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br> /* transform polygon and click point to screen space */<br />
<br> var screen=this.mapper.toScreenSpace(set);<br />
<br> var point =this.mapper.toScreenSpace([pt])[0];<br />
<br> //and add first point as last point<br />
<br> var L=set.length;<br />
<br> screen.push(screen[0]);<br />
<br> </span><br />
</code></code></blockquote>And find that point!<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br> var minval=10000000;<br />
<br> var minfit=-1;<br />
<br> var x2=point.x;<br />
<br> var y2=point.y;<br />
<br> var i;<br />
<br> for( i=0;i<L;i++){<br />
<br> //remeber the milk!<br />
<br> /** same login. find distance from point to segment */<br />
<br> var x1=screen[i].x;<br />
<br> var y1=screen[i].y;<br />
<br><br />
<br> var x3=screen[i+1].x;<br />
<br> var y3=screen[i+1].y;<br />
<br> <br />
<br> var vx1=x2-x1;<br />
<br> var vy1=y2-y1;<br />
<br> var len=Math.sqrt(vy1*vy1+vx1*vx1);<br />
<br><br />
<br> var v1=this._norm(vx1,vy1,len);<br />
<br><br />
<br> var vx2=x3-x1;<br />
<br> var vy2=y3-y1;<br />
<br> var len2=Math.sqrt(vy2*vy2+vx2*vx2);<br />
<br> //if distance to test point is greater that distance to other side of the egde - skip <br />
<br> if(len>len2){<br />
<br> continue;<br />
<br> }<br />
<br> var v2=this._norm(vx2,vy2);<br />
<br><br />
<br> var dot=this._dot(v1,v2);<br />
<br> var angle=(Math.acos(dot));<br />
<br> /** we need positive angle and not greater that 60 degress*/<br />
<br> if(angle>Math.PI/3)continue;<br />
<br> var sa=Math.sin(angle);<br />
<br><br />
<br> var perp=sa*len;<br />
<br> // so this is best fit for point<br />
<br> if(perp<minval){<br />
<br> minval=perp;<br />
<br> minfit=i;<br />
<br> }<br />
<br> }<br />
<br> //and distance must be not very big<br />
<br> if(minval<50){<br />
<br> return minfit;<br />
<br> }<br />
<br> return -1;<br />
<br> </span><br />
</code></code></blockquote>Done, all other work you can see by yourself in scripts<br />
<h2>Step3. Delete Verticle</h2> just click the marker :)<br />
<h2>Google polygon editor variant 1</h2> <a href="http://www.kashey.ru/pages/maps/basic_poly_editor_test.php">online version</a>, you can drag markers and you must<br />
CLICK the map near edge to spawn new verticle<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br> GEvent.addListener(this.map, "click", function(overlay,point){<br />
<br> _this.newMarker(point,'auto');<br />
<br> });<br />
<br> </span><br />
</code></code></blockquote><h2>Google polygon editor variant 2</h2> <a href="http://www.kashey.ru/pages/maps/basic_poly_editor_test.php?v2">online version</a>, you can drag markers and just move mouse over edge to create new verticle.<br><br />
if you click spawned marker - it become normal. So variant like one you can see on <a href="http://wikimapia.org">wikimapia.org</a><br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br> GEvent.addListener(this.map, "mousemove", function(point){<br />
<br> if(!point)return;<br />
<br> //get current point and move it by 30 pixel to bottom<br />
<br> var sx=_this.mapper.toScreenSpace([point])[0];<br />
<br> sx.y+=30;<br />
<br> point=_this.mapper.toMapSpace([sx])[0]<br />
<br> //remove old marker<br />
<br> if(_this.dragMarker){<br />
<br> _this.removeHold(_this.dragMarker.holdpoint);<br />
<br> _this.map.removeOverlay(_this.dragMarker);<br />
<br> delete _this.dragMarker;<br />
<br> }<br />
<br> //find where we can(if we can) place new marker<br />
<br> var position=_this.findBestPosition(point);<br />
<br> if(position<-1)return;<br />
<br> else{<br />
<br> //and place it! <br />
<br> var marker=_this.newMarker(point,'auto');<br />
<br> _this.dragMarker=marker; <br />
<br> }<br />
<br> });<br />
<br> </span><br />
</code></code></blockquote><h2>Google polygon editor variant 3</h2> <a href="http://www.kashey.ru/pages/maps/basic_poly_editor_test.php?v3">online version</a>, you can drag markers and.. hm who add green markers where?<br><br />
How i do it? Just perform some preprocess before render <br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br>myEditablePolygon.prototype.preprocess = function(){<br />
<br> this.removeGhosts();<br />
<br> this.ghostMarkers=[];<br />
<br> var i=0;<br />
<br> var verts=[];<br />
<br> var inverts=this.verticles.slice(0);<br />
<br> var L=inverts.length;<br />
<br> inverts.push(inverts[0]);<br />
<br> // lets to trought verticles and create some markers in middle of edges<br />
<br> for ( i=0;i<L;i++){<br />
<br> var _this=this;<br />
<br> (function(){<br />
<br> var x1=inverts[i]._x;<br />
<br> var y1=inverts[i]._y;<br />
<br> var x2=inverts[i+1]._x;<br />
<br> var y2=inverts[i+1]._y;<br />
<br> var point = new GLatLng(x1+(x2-x1)/2,y1+(y2-y1)/2);<br />
<br> <br />
<br> //blue marker from google`s exmaple<br />
<br> var blueIcon = new GIcon(G_DEFAULT_ICON);<br />
<br> blueIcon.image = "http://gmaps-samples.googlecode.com/svn/trunk/markers/blue/blank.png";<br />
<br> <br />
<br> var marker = new GMarker(point, {draggable: true,icon:blueIcon});<br />
<br> marker.parent=_this;<br />
<br> _this.ghostMarkers.push(marker);<br />
<br> _this.map.addOverlay(marker);<br />
<br> <br />
<br> //on mouse down - remove blue marker and create new normal marker<br />
<br> GEvent.addListener(marker, "mousedown", function() {<br />
<br> this.parent.map.removeOverlay(this);<br />
<br> this.parent.newMarker(this.getPoint(),'auto')<br />
<br> });<br />
<br> })();<br />
<br> }<br />
<br> return this.verticles;<br />
<br>}<br />
<br></span><br />
</code></code></blockquote> <br />
<br />
<div style="border: 2px dotted gray;"> <b>Homework:</b> inherit from <a href="http://www.kashey.ru/maps/howto-reduce-polygon/">smartPolygon</a> to perform more complex solution <br />
</div></div><br><br><br />
<div style="border: 2px dotted gray;"> All of sections, i list here, is allready done by me. I need just to wrap code into article, and, yap, english article :)<br><br />
If you want to know more - <a href="mailto://thekashey@gmail.com">ask</a><br />
</div><br><center><h1 style="font-size: 12px; padding: 4px; background-color: rgb(192, 96, 0); margin: 0pt;">lest make a Very simple polygon editor</h1></center>Kasheyhttp://www.blogger.com/profile/06277598811675041059noreply@blogger.com0tag:blogger.com,1999:blog-8438981525416634522.post-64819347028889707612010-03-16T05:57:00.001-07:002010-03-16T05:57:56.858-07:00how to reduce polygon to fit current zoom level resolution<div> <div style="border: 2px dotted gray;"> <p><b>Mission:</b> create polygon of 1000 verticles and display it w\o lags</p> <p><b>Goal:</b> control LOD in real time</p> </div> <h2>Step1. Create Polygon</h2> lets create circle<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br> //lests create circle<br />
<br> var polydata=[];<br />
<br> var Nverts =1000;<br />
<br> var step =Math.PI*2/Nverts;<br />
<br> var radius =0.03;<br />
<br> var i;<br />
<br> for(i=0;i<Nverts;i++){<br />
<br> <br />
<br> polydata.push(new GLatLng(37.4419 +radius*Math.cos(i*step),<br />
<br> -122.1419+radius*Math.sin(i*step)));<br />
<br> }<br />
<br> </span><br />
</code></code></blockquote>next add it to map<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br> return new smartPolygon(createCircle(),map,new CoordinatesMapper(map));<br />
<br> </span><br />
</code></code></blockquote>setup redraw on zoom change<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br> GEvent.addListener(map, "zoomend", function(){<br />
<br> _this.render();<br />
<br> });<br />
<br> </span><br />
</code></code></blockquote>and change complexity on zoom change<br />
<blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br> //convert data to screen space<br />
<br> var set=this.mapper.toScreenSpace(this.data);<br />
<br> var inL=set.length;<br />
<br> //reduce<br />
<br> set=this.performReduce(set);<br />
<br> var outL=set.length;<br />
<br> /** log **/<br />
<br> document.getElementById("message").innerHTML='polygon reduced from '+inL+' to '+outL;<br />
<br> //convert back to map space and return<br />
<br> return this.mapper.toMapSpace(set);<br />
<br> </span><br />
</code></code></blockquote><h2>But how to change it?</h2> <img src="/img/maps/reduce1.png"><br />
<p>having points 1,2,3 and 4 we need to remove point 2 but keep point 3.</p> <p>Lets create vector from 1 to 3, and from 1 to 4. We need to caclucate perpendicular P from 3 to L</p> <p>If it is greater that some delta - so we have <u>visible shift</u>. And we store point 1 and 3 in result set.<br />
</p><blockquote style="border-left: 2px solid rgb(238, 238, 238); background-color: rgb(170, 170, 170);"><code><code><span style="color: rgb(0, 0, 0);"><br />
<br />
<br> var resultset=[];<br />
<br> var L=dataset.length;<br />
<br> resultset.push(dataset[0]);<br />
<br> dataset.push(dataset[L-1]); <br />
<br> var cur=0;<br />
<br> var j=0;<br />
<br> for(j=1;j<L;j++)<br />
<br> {<br />
<br> var x1=dataset[cur].x;<br />
<br> var y1=dataset[cur].y;<br />
<br><br />
<br> var x2=dataset[j ].x;<br />
<br> var y2=dataset[j ].y;<br />
<br><br />
<br> var x3=dataset[j+1].x;<br />
<br> var y3=dataset[j+1].y;<br />
<br> //first points must be different in screen space <br />
<br> if(!(((Math.round(x1)==Math.round(x2)) && (Math.round(y1)==Math.round(y2))) ||<br />
<br> ((Math.round(x2)==Math.round(x3)) && (Math.round(y2)==Math.round(y3)))))<br />
<br> {<br />
<br><br />
<br> /*<br />
<br> goal - create two vectors -<br />
<br> from current to test point<br />
<br> and from current to next point<br />
<br> we need to drop a perpendicular from test point to cur-last vector<br />
<br> just simple mathematics<br />
<br> */<br />
<br> var vx1=x2-x1;<br />
<br> var vy1=y2-y1;<br />
<br> var len=Math.sqrt(vy1*vy1+vx1*vx1);<br />
<br><br />
<br> var v1=this._norm(vx1,vy1,len);<br />
<br><br />
<br> var vx2=x3-x1;<br />
<br> var vy2=y3-y1;<br />
<br> var v2=this._norm(vx2,vy2);<br />
<br><br />
<br> var dot=this._dot(v1,v2);<br />
<br> var sa=Math.sin(Math.acos(dot));<br />
<br><br />
<br> var perp=sa*len;<br />
<br> //so if perpendicular is bigger than delta, and length of vector is bigger than delta<br />
<br> //add point to result set<br />
<br> if(Math.abs(perp)>=delta && len>=delta)<br />
<br> {<br />
<br> cur=j;<br />
<br> resultset.push(dataset[j]);<br />
<br><br />
<br> }<br />
<br> }<br />
<br> }<br />
<br> </span><br />
</code></code></blockquote>Next iteration will begin from point 3.<p></p> <h2>In any case - it works!</h2> and, all of this <a href="http://www.kashey.ru/pages/maps/reduce_polygon_test.php">you can see online</a><br />
<br><br />
Just zoomin and out, and set different deltas(end press enter) to see how it works!<br />
<br />
<div style="border: 2px dotted gray;"> <b>Homework:</b> inherit smartPolygon from GOverlay to produce more transparent solution<br />
</div></div><br><br><br />
<div style="border: 2px dotted gray;"> All of sections, i list here, is allready done by me. I need just to wrap code into article, and, yap, english article :)<br><br />
If you want to know more - <a href="mailto://thekashey@gmail.com">ask</a><br />
</div><br><center><h1 style="font-size: 12px; padding: 4px; background-color: rgb(192, 96, 0); margin: 0pt;">How to reduce polygon complexy and be happy</h1></center>Kasheyhttp://www.blogger.com/profile/06277598811675041059noreply@blogger.com0