<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-8689603183703421710</id><updated>2011-08-03T18:18:38.538-07:00</updated><category term='weather'/><category term='doppler'/><category term='google app engine'/><category term='imagemagick'/><category term='datastorage'/><category term='objectify'/><category term='php'/><category term='persistence'/><category term='gae'/><title type='text'>Tero Nurminen</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://teronurminen.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8689603183703421710/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://teronurminen.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Tero Nurminen</name><uri>http://www.blogger.com/profile/07185212759382984274</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>2</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-8689603183703421710.post-1190734747974461897</id><published>2010-03-08T23:29:00.000-08:00</published><updated>2010-03-09T11:01:53.388-08:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='objectify'/><category scheme='http://www.blogger.com/atom/ns#' term='persistence'/><category scheme='http://www.blogger.com/atom/ns#' term='datastorage'/><category scheme='http://www.blogger.com/atom/ns#' term='google app engine'/><category scheme='http://www.blogger.com/atom/ns#' term='gae'/><title type='text'>GAE/J datastorage persistence with Objectify framework in practice</title><content type='html'>I am quite a newcomer for Google App Engine platform and just getting familiar with the basic concepts. Google App Engine is a platform for developing and hosting web applications in Google-managed data centers. One of the most important features in App Engine is using the DatastorageService where developers can persistet their application objects in BigTable clusters. Many who have worked previously with JDO and JPA might find the Datanucleus implementations worth trying but I come from PHP background which lacks those high level abstractions anyway. JDO, and especially JPA is designed for managing traditional relational database models which fights against the design philosophy of Google Datastorage. Moreover, both persistence interfaces are developed over a long period and provide backward compatibility increasing the historical burden of their implementations.&lt;br /&gt;&lt;br /&gt;Google Datastorage is built on BigTable which is addresses highly scalability and geographically dispersed locations.  In constrast to RDBMS'es, Google Datastorage has other principles one must take into account when persisting objects in the cloud. Google provides DatastoreService, but it has more machine-friendly than human-friendly query interface&lt;a href="http://code.google.com/p/objectify-appengine/"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Fortunately, there is at least three frameworks providing higher abstraction for using the DatastoreService. &lt;a href="http://code.google.com/p/objectify-appengine/"&gt;Objectify&lt;/a&gt;, &lt;a href="http://code.google.com/p/twig-persist/"&gt;Twig&lt;/a&gt; and &lt;a href="http://code.google.com/p/simpleds/"&gt;SimpleDS&lt;/a&gt; address the complexity of DatastoreService by providing a human-friendly query interface for applications objects' persistence.&lt;br /&gt;&lt;br /&gt;Selecting between those three took me a few hours as I was browsing through their discussion groups and evaluating the quality of Wiki. Especially, effort the developer &lt;a href="http://groups.google.com/groups/profile?enc_user=yjPEIRMAAABY6fDofa0rTjyZxpXbZJ8UCrTwKYbraL2wE_wkV0bY1A"&gt;Jeff Schnitzer&lt;/a&gt; has put on speaking in favor&lt;a href="http://groups.google.com/group/google-appengine-java/browse_thread/thread/f20d922ffecb310c?pli=1"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; of Objectify and defending its design principles against other candidates made me an impression that the project is really worth trying :)&lt;br /&gt;&lt;span style="font-size:130%;"&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Let's get started with Objectify&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;The following tutorial is written for developers who have no previous experience with Google App Engine. If you feel you're more experienced, just leap the steps you feel not suitable for your purposes.&lt;br /&gt;&lt;br /&gt;This stupid app demonstrates a simple &lt;span style="font-style: italic;"&gt;one-to-many relationship&lt;/span&gt; between a &lt;span style="font-style: italic;"&gt;car&lt;/span&gt; and its &lt;span style="font-style: italic;"&gt;owner&lt;/span&gt;, and how to manage their persistence with Objectify.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_zrZmCfhQeq4/S5Yr_iZpzgI/AAAAAAAAAA8/ilMws3uYrNY/s1600-h/Screen+shot+2010-03-09+at+1.07.16+PM.png"&gt;&lt;img style="cursor: pointer; width: 320px; height: 129px;" src="http://4.bp.blogspot.com/_zrZmCfhQeq4/S5Yr_iZpzgI/AAAAAAAAAA8/ilMws3uYrNY/s320/Screen+shot+2010-03-09+at+1.07.16+PM.png" alt="" id="BLOGGER_PHOTO_ID_5446589169917349378" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Relational domain model&lt;/span&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;&lt;br /&gt;&lt;br /&gt;1. Install Eclipse and &lt;/span&gt;&lt;a style="font-weight: bold;" href="http://code.google.com/eclipse/docs/download.html"&gt;Google Plugin for Eclipse&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;2. Create new GAE project&lt;/span&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_zrZmCfhQeq4/S5YGQBvMprI/AAAAAAAAAAU/_9UGOSRq-gE/s1600-h/Screen+shot+2010-03-09+at+10.26.08+AM.png"&gt;&lt;img style="cursor: pointer; width: 284px; height: 320px;" src="http://1.bp.blogspot.com/_zrZmCfhQeq4/S5YGQBvMprI/AAAAAAAAAAU/_9UGOSRq-gE/s320/Screen+shot+2010-03-09+at+10.26.08+AM.png" alt="" id="BLOGGER_PHOTO_ID_5446547671765264050" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Name your project and package as you wish but deselect Use Google Web Toolkit and then click Finish&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;3. Download Objectify 2.0.2 and copy the jar into lib-path and add it also to build path&lt;/span&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_zrZmCfhQeq4/S5YJ9edp5NI/AAAAAAAAAAk/ACkT5FLxU8M/s1600-h/Screen+shot+2010-03-09+at+10.33.11+AM.png"&gt;&lt;img style="cursor: pointer; width: 258px; height: 320px;" src="http://3.bp.blogspot.com/_zrZmCfhQeq4/S5YJ9edp5NI/AAAAAAAAAAk/ACkT5FLxU8M/s320/Screen+shot+2010-03-09+at+10.33.11+AM.png" alt="" id="BLOGGER_PHOTO_ID_5446551751105307858" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;4. You can test run your application and get the "hello world" printed on browser screen&lt;/span&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_zrZmCfhQeq4/S5YLimQosJI/AAAAAAAAAAs/sls3-DfQo-I/s1600-h/Screen+shot+2010-03-09+at+10.48.41+AM.png"&gt;&lt;img style="cursor: pointer; width: 320px; height: 143px;" src="http://3.bp.blogspot.com/_zrZmCfhQeq4/S5YLimQosJI/AAAAAAAAAAs/sls3-DfQo-I/s320/Screen+shot+2010-03-09+at+10.48.41+AM.png" alt="" id="BLOGGER_PHOTO_ID_5446553488364974226" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Everything seems to be ok, so let's proceed with some real code&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;5. Let's create an entity class Person which stores the car owner instance&lt;/span&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;import javax.persistence.Id;&lt;br /&gt;import com.googlecode.objectify.annotation.Entity;&lt;br /&gt;&lt;br /&gt;@Entity&lt;br /&gt;public class Person {&lt;br /&gt;@Id private Long id;&lt;br /&gt;private String name;&lt;br /&gt;/* setters and getters omitted for brevity */&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;Notes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;@Entity is not obligatory for Objectify but good for distinguishing entities easily&lt;/li&gt;&lt;li&gt;@Id is the only required parameter and can be Long, long or String. I prefer Long, because id is auto generated if entity does not exist&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;6. Create Car entity class&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;br /&gt;import javax.persistence.Id;&lt;br /&gt;import com.googlecode.objectify.Key;&lt;br /&gt;import com.googlecode.objectify.annotation.Entity;&lt;br /&gt;import com.googlecode.objectify.annotation.Parent;&lt;br /&gt;&lt;br /&gt;@Entity&lt;br /&gt;public class Car {&lt;br /&gt;@Id public Long id;&lt;br /&gt;private String license;&lt;br /&gt;&lt;br /&gt;@Parent public Key&amp;lt;Person&amp;gt; owner;&lt;br /&gt;&lt;br /&gt;private Car() {}&lt;br /&gt;public Car(Key&amp;lt;Person&amp;gt; owner) {&lt;br /&gt;this();&lt;br /&gt;this.owner = owner;&lt;br /&gt;}&lt;br /&gt;/* setters and getters omitted for brevity */&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;Notes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;@Parent informs that the entity is in many-one relationship with Person (unlike in JDO/JPA, only children entities annotate their parent entities). Parent must be a type of Objectify Key&lt;t&gt; data type.&lt;/t&gt;&lt;/li&gt;&lt;li&gt;Car constructor must be created with Key&amp;lt;person&amp;gt; argument to set the owner&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;7. Now we're going to implement some logic. Open ObjectifyCarsServlet.java and replace the code printing "Hello, world"&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public void doGet(HttpServletRequest req, HttpServletResponse resp)&lt;br /&gt;throws IOException {&lt;br /&gt;&lt;b&gt;&lt;br /&gt;resp.setContentType("text/plain");&lt;br /&gt;resp.getWriter().println("Hello, world");&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;...with...&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;public void doGet(HttpServletRequest req, HttpServletResponse resp)&lt;br /&gt;throws IOException {&lt;br /&gt;&lt;b&gt;&lt;br /&gt;resp.setContentType("text/html");&lt;br /&gt;&lt;br /&gt;String html = "&amp;lt;form&amp;gt;Owner id (blank for new)&amp;lt;input name="id" type="text"&amp;gt; "+&lt;br /&gt;"Car license &amp;lt;input name="license" type="text"&amp;gt;&amp;lt;input type="submit"&amp;gt;&amp;lt;/form&amp;gt;";&lt;br /&gt;resp.getWriter().println(html);&lt;br /&gt;&lt;br /&gt;if( req.getParameter("license") == null ) {&lt;br /&gt;return;&lt;br /&gt;}&lt;br /&gt;if( req.getParameter("license").length() == 0 ) {&lt;br /&gt;resp.getWriter().println("License not set");&lt;br /&gt;return;&lt;br /&gt;}&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;Notes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;You should have basic form with car id and license fields when you restart the application and browse it&lt;/li&gt;&lt;li&gt;Owner id's are auto-generated by Objectify so you can not give distinctive name by yourself&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;8. Then... we must register Objectify entities. Write a static block for ObjectifyCarServlet-class:&lt;/span&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;@SuppressWarnings("serial")&lt;br /&gt;public class ObjectifyCarsServlet extends HttpServlet {&lt;br /&gt;&lt;b&gt;&lt;br /&gt;static {&lt;br /&gt;ObjectifyService.register(Person.class);&lt;br /&gt;ObjectifyService.register(Car.class);&lt;br /&gt;}&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;public void doGet(HttpServletRequest req, HttpServletResponse resp)&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;Notes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;For further reading about registering entities, go to &lt;a href="http://code.google.com/p/objectify-appengine/wiki/BestPractices"&gt;Objectify Best Practices&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;9. Now let's make some logic for GET-requests. Paste the following code block bottom of doGet()-method:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;br /&gt;Objectify ofy = ObjectifyService.begin();&lt;br /&gt;&lt;br /&gt;Person owner = new Person();&lt;br /&gt;if( req.getParameter("id") != null &amp;amp;&amp;amp; req.getParameter("id").length() &gt; 0 ) {&lt;br /&gt;owner = ofy.find(Person.class, Long.parseLong(req.getParameter("id")) );&lt;br /&gt;}&lt;br /&gt;else&lt;br /&gt;ofy.put(owner);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;Notes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Create Objectify object, &lt;span style="font-style: italic;"&gt;ofy&lt;/span&gt;, for persistence manager&lt;/li&gt;&lt;li&gt;Then, we either find an existing &lt;span style="font-style: italic;"&gt;owner &lt;/span&gt;or create a new one&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;10. Continue pasting with...&lt;/span&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;br /&gt;Key&amp;lt;Person&amp;gt; ownerKey = new Key&amp;lt;Person&amp;gt;(Person.class, owner.getId());&lt;br /&gt;Car car = new Car(ownerKey);&lt;br /&gt;car.setLicense(req.getParameter("license"));&lt;br /&gt;ofy.put(car);&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;Notes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Declare new &lt;span style="font-style: italic;"&gt;&lt;/span&gt;&lt;span style="font-style: italic;"&gt;ownerKey &lt;/span&gt;data-storage key for the &lt;span style="font-style: italic;"&gt;owner &lt;/span&gt;object&lt;/li&gt;&lt;li&gt;Create a &lt;span style="font-style: italic;"&gt;car &lt;/span&gt;instance for the &lt;span style="font-style: italic;"&gt;&lt;span style="font-style: italic;"&gt;&lt;span style="font-style: italic;"&gt;&lt;span style="font-style: italic;"&gt;&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;owner&lt;/span&gt;&lt;/li&gt;&lt;li&gt;Persist the &lt;span style="font-style: italic;"&gt;car&lt;/span&gt; instance with Objectify.put()&lt;br /&gt;&lt;span style="font-style: italic;"&gt;&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;11. Finally, we display all cars stored in data-storage:&lt;/span&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;pre&gt;&lt;br /&gt;Query&amp;lt;Car&amp;gt; q = ofy.query(Car.class).ancestor(ownerKey);&lt;br /&gt;resp.getWriter().println("&amp;lt;pre&amp;gt;Cars for owner id "+owner.getId()+":");&lt;br /&gt;for( Car c : q )&lt;br /&gt;resp.getWriter().println(c.getLicense());&lt;br /&gt;&lt;/pre&gt;&lt;/blockquote&gt;&lt;br /&gt;Notes:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;ofy.query(Car.class) searches all Car.class entities from the database&lt;/li&gt;&lt;li&gt;ancestor(ownerKey) omits all Car entities which don't have the requested owner from the result set (it took me quite a while to find out that Objectify Wiki lacks an example of using Query&lt;t&gt;.ancestor() method)&lt;/t&gt;&lt;/li&gt;&lt;li&gt;Query&lt;t&gt; is a &lt;span style="font-style: italic;"&gt;&lt;span style="font-style: italic;"&gt;&lt;/span&gt;collection &lt;/span&gt;type so it can be iterated through and strip down to single entities&lt;/t&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;There you go, the result could look something like this with browser after populating a few entries to the data-storage...&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_zrZmCfhQeq4/S5YaMZtIQSI/AAAAAAAAAA0/8HTPeaj4G_k/s1600-h/Screen+shot+2010-03-09+at+11.46.36+AM.png"&gt;&lt;img style="cursor: pointer; width: 320px; height: 140px;" src="http://2.bp.blogspot.com/_zrZmCfhQeq4/S5YaMZtIQSI/AAAAAAAAAA0/8HTPeaj4G_k/s320/Screen+shot+2010-03-09+at+11.46.36+AM.png" alt="" id="BLOGGER_PHOTO_ID_5446569599712117026" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8689603183703421710-1190734747974461897?l=teronurminen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://teronurminen.blogspot.com/feeds/1190734747974461897/comments/default' title='Lähetä kommentteja'/><link rel='replies' type='text/html' href='http://teronurminen.blogspot.com/2010/03/gaej-data-storage-persistence-with.html#comment-form' title='0 kommenttia'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8689603183703421710/posts/default/1190734747974461897'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8689603183703421710/posts/default/1190734747974461897'/><link rel='alternate' type='text/html' href='http://teronurminen.blogspot.com/2010/03/gaej-data-storage-persistence-with.html' title='GAE/J datastorage persistence with Objectify framework in practice'/><author><name>Tero Nurminen</name><uri>http://www.blogger.com/profile/07185212759382984274</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_zrZmCfhQeq4/S5Yr_iZpzgI/AAAAAAAAAA8/ilMws3uYrNY/s72-c/Screen+shot+2010-03-09+at+1.07.16+PM.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-8689603183703421710.post-1628269704848650900</id><published>2009-07-11T03:48:00.000-07:00</published><updated>2009-07-11T04:29:26.885-07:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='weather'/><category scheme='http://www.blogger.com/atom/ns#' term='php'/><category scheme='http://www.blogger.com/atom/ns#' term='doppler'/><category scheme='http://www.blogger.com/atom/ns#' term='imagemagick'/><title type='text'>Doppler image animation with PHP and ImageMagick</title><content type='html'>Gnome Weather panel provides a radar map using an image URL to show a current weather forecast. Radar map has a support for animated gifs which drove me to develop a little PHP script to make a doppler animation.&lt;br /&gt;&lt;br /&gt;I found out that Foreca provides a set of dopper images showing the next 7 hours forecast in Finland. Images have well formed filenames, so there's an easy way to make a script to download them periodically.&lt;br /&gt;&lt;br /&gt;Dependencies: ImageMagick and a truetype font&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;span style="color: rgb(0, 0, 0);"&gt; &lt;span style="color: rgb(0, 0, 187);"&gt;&lt;span style="color: rgb(255, 128, 0);"&gt;// Nice little script I made courtesy to Gnome Weather Report panel's radar image&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;code&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;chdir&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;(&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;dirname&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;(&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;__FILE__&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;exec&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;(&lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'rm *.gif'&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;);&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(255, 128, 0);"&gt;// download doppler images (foreca provides next 7 hours ahead)&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;for( &lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$i &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;= &lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;0&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;; &lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$i &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;&lt; &lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;7&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;; &lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$i&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;++ ) {&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$hour &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;= &lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;date&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;(&lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'H'&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;)+&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$i&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$hour &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;= &lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$hour&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;%&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;24&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$hour &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;= &lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;str_pad&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;(&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$hour&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;, &lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;2&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;, &lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;"0"&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;, &lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;STR_PAD_LEFT&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;);&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$date &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;= &lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;date&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;(&lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'Ymd'&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;).&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$hour&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$file &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;= &lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'fin-'&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;.&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$date&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;.&lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'.gif'&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(255, 128, 0);"&gt;// thanks to taloussanomat :)&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$url &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;= &lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'http://http.foreca.com/xfer/taloussanomat/precipanim/'&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;.&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$file&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;exec&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;( &lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'wget '&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;.&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$url &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;);&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$date_str &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;= &lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$hour&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;.&lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;':00'&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(255, 128, 0);"&gt;// make a date image&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;exec&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;( &lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'convert -size 50x20 xc:transparent -font '&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;.&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;dirname&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;(&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;__FILE__&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;).&lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'/tahoma.ttf -fill red '&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;.&lt;br /&gt;  &lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'-pointsize 14 -draw "text 5,15 \''&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;.&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$date_str&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;.&lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'\'" date.png ' &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;);&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(255, 128, 0);"&gt;// add date image on doppler image&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;exec&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;( &lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'composite -gravity southeast date.png '&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;.&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$file&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;.&lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;' '&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;.&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;$file &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(255, 128, 0);"&gt;// generate animation (1s delay, infinite loop)&lt;br /&gt;&lt;/span&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;exec&lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;( &lt;/span&gt;&lt;span style="color: rgb(221, 0, 0);"&gt;'convert -delay 100 -loop 0 fin-*.gif doppleranimation.gif' &lt;/span&gt;&lt;span style="color: rgb(0, 119, 0);"&gt;);&lt;br /&gt;&lt;/span&gt; &lt;/span&gt; &lt;/span&gt;&lt;/code&gt;&lt;br /&gt;&lt;code&gt;&lt;span style="color: rgb(0, 0, 0);"&gt;&lt;span style="color: rgb(0, 0, 187);"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;The script can be added to crontab, to make the doppler animation refresh once an hour. Working animation can be seen here &lt;a href="http://panic.fi/%7Etero/weather/doppleranimation.gif"&gt;http://panic.fi/~tero/weather/doppleranimation.gif&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/8689603183703421710-1628269704848650900?l=teronurminen.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://teronurminen.blogspot.com/feeds/1628269704848650900/comments/default' title='Lähetä kommentteja'/><link rel='replies' type='text/html' href='http://teronurminen.blogspot.com/2009/07/doppler-image-animation-with-php-and.html#comment-form' title='0 kommenttia'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/8689603183703421710/posts/default/1628269704848650900'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/8689603183703421710/posts/default/1628269704848650900'/><link rel='alternate' type='text/html' href='http://teronurminen.blogspot.com/2009/07/doppler-image-animation-with-php-and.html' title='Doppler image animation with PHP and ImageMagick'/><author><name>Tero Nurminen</name><uri>http://www.blogger.com/profile/07185212759382984274</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>
