Table of Contents

A blog about Software Development, Astronomy and Everything

Asterope - first milestone

First milestone of Asterope was just released.

Asterope is 'just another rewrite' of OpenCoeli. I decided to migrate from Groovy programming language to Scala, dynamic typing is just bad. Also project was renamed to Asterope. I also started to more pragmatic work schedule, milestone should be released every three months.

This release does not contains any GUI. It just generates maps from command line. Major work was done on architecture, build process and database. It should be solid base for next releases with GUI.

Download from new project page

RangeSet and huge datasets

Most of collections does not use memory efficiently. But there is simple compression trick to store virtually unlimited data sets. And there are practical implementations.

First little bit of theory. Lets have Java SortedSet of Long numbers. It have a few basic features: * Values in set are unique, there are duplicates. * Contains() operation is indexed and fast. * Values in set are sorted lowest to highest.

This data structure can be implemented in various way. Most common is TreeSet which uses BTrees structure. More modern variant is SkipList. But all of those structures does not use memory very efficiently. Their memory consumption grows linearly with their size.

To save same memory one can store values in sorted long[] array and use binary search to find values. This is consuming way less memory, because object are not created, and only primitive values are stored. Trade off is the same as with ArrayList, inserts require moving arrays around and are generally very slow.

Other way to save memory can be BitSet. But its usage is very limited. More smart structures are compressed BitSets but their usage is even more limited.

Ranges

In case data are grouping in continuous intervals compression is very simple. Only first and last values needs to be stored. So instead of bunch of number item in set will be:

class Range{
	long first;
	long last;
}

This brings some new problems. First it needs to handle range overlaps on inserts. Second is contains operation for single number.

There are plenty of implementations. Most notable is LongRangeSet from Primitive Collections for Java. This implementation uses Range object stored in sorted list.

Completely without object overhead is this RangeSet It stores range boundaries in sorted long[] array. Range starts are at even positions, range ends are at odd position. This organization is very effective in memory consumption and fast for read only operations. But inserts are taking longer.

And there is even implementation for .Net

Operations

Data compressed in memory are nice, but howto process them without doing decompression and running out of memory? Iteration is quite simple to imagine, but what about some others?

Most typical spatial operations are logical operations on items in set: union, intersection and complement. Those operations produce other set. This operations can be implemented very efficiently.

LongRangeSet

And here is my implementation of LongRangeSet. It is part of bigger project Healpix-RangeSet. In this project it stores huge set of astronomical spatial data.

By replacing ArrayList by RangeSet performance of Healpix skyrocketed. Memory consumption decreased from gigabytes to megabytes. And area generation speed up from hours to seconds.

LongRangeSet also implements union, intersection and complement very efficiently. Each operation uses only one pass over ranges and new set is constructed on the fly.

Using Ant and Javac without installation

Installing Ant and JDK can be pain. This post shows howto make Ant Java compiler part of your project. So it does not require installation. Only one external dependency needed to make build will be JRE. This setup makes it also much easier for new comers to start on project

Trick is to place Ant jars into your classpath and use manual script files to call them. Javac from JDK have many dependencies, but it can be replaced with compiler from Eclipse.

Step 1

Download Ant and place its jar files into tools subdirectory. For most purposes you will need those files: ant.jar, ant-launcher.jar, ant-testutil.jar and ant-trax.jar

Step 2

Create custom script to launch Ant. Create ant.bat file in root of your project. This will launch ant and forward all parameters to it. This launcher also adds all jars in tools and lib directory into classpath.

java -Djava.ext.dirs=tools;lib -Xmx256m -jar tools/ant-launcher.jar  %1 %2 %3 %4 %5 %6 %7 %8 %9

And for Linux use ant.sh

#!/bin/sh
java -Djava.ext.dirs=tools;lib  -Xmx256m  -jar tools/ant-launcher.jar  $@

Also set file executable flat

chmod 0755 ant.sh
svn propset svn:executable ON ant.sh

Step 3

Get Eclipse JDT Compiler. It is only 1.6 MB jar without any dependencies. Download it at [http://download.eclipse.org/eclipse/downloads/drops/R-3.5.1-200909170800/index.php Eclipse Project Downloads] (search for JDT Core Batch Compile or ecj-3.5.1.jar). Then place this jar into tools directory

Step 4

Use Eclipse JDT Compiler. To make use out of new compiler you need to set one property in build.xml:

<!--
 Use embedded Eclipse JDT javac. So JDK dont have to be installed.
 This may cause problems on some IDEs. Comment it out then.
-->
<property name="build.compiler" value="org.eclipse.jdt.core.JDTCompilerAdapter"/>

Ant will use Eclipse JDT Compiler, so it will not require JDK. It will still display message 'tools.jar not found' but will compile just fine.

NOTE

In some IDEs you may have problem because ECJ will not be in Ant classpath. Easiest workaround is to unset this property and install JDT. Other solution is to extend Ant path in your IDE to include ECJ.

JMinuit and optional error output.

JMinuit is very good library for minimum on given function. In Asterope it replaced in house build library, and it have way better result.

Only one problem is little bit annoying. Library have hard coded error output to System.err and it produces annoying messages in console while application is running. There is even bug report created two years ago. But library seems to be unmaintained.

So I sit down and fixed this problem. It replaces calls to System.err with an optional wrapper. To get rid off messages in console, just set static variable to null. Or you can supply your own PrintStream and redirect output to file.

       org.freehep.math.minuit.MnPrint.stdOut = null;
       org.freehep.math.minuit.MnPrint.errOut = null;

Here is zip file containing patch and jar. I will try to push this into trunk.

freehep-jminuit-optional-error-output.zip

Piccolo and SVG

I was thinking how to improve symbols on map. Star clusters, nebulas etc have complicated signs and it is difficult to draw them with plain Java2d. I decided to use SVG vector graphic to draw those symbols. Using vector editor is way way easier then coding in Java2d.

Asterope is using Piccolo 2d framework to manage map scene. But this does not support SVG yet. It is in TODO list, but no serious implementation is done yet.

So I wrote one. It is using lightweight SVG renderer SVG Salamander. It is very fast, and adds only 300kb jar to dependencies.

I used PImage internals to see howto hook into Piccolo. Also SVGIcon was usefull to see howto paint SVG. This is first shot implementation, but already supports rotation, transformations, animations …

Here is code with testing main method:

package org.asterope.util;
 
import com.kitfox.svg.SVGCache;
import com.kitfox.svg.SVGDiagram;
import com.kitfox.svg.SVGException;
import com.kitfox.svg.SVGUniverse;
import com.kitfox.svg.app.beans.SVGIcon;
import edu.umd.cs.piccolo.PNode;
import edu.umd.cs.piccolo.util.PBounds;
import edu.umd.cs.piccolo.util.PPaintContext;
import edu.umd.cs.piccolox.PFrame;
 
import java.awt.*;
import java.net.URI;
import java.net.URL;
 
 
/**
 * Piccolo node which draws SVG using SVGIcon from SVG Salamander
 */
public class PSvgNode extends PNode {
 
    /**
     * The property name that identifies a change of this node's SVG URI
     * Both old and new value will be set correctly
     */
    public static final String PROPERTY_IMAGE = "image";
    public static final int PROPERTY_CODE_IMAGE = 1 << 15;
 
 
    protected SVGUniverse svgUniverse = SVGCache.getSVGUniverse();
 
    protected URI svgURI = null;
 
    public PSvgNode(String resPath) {
        setSvgResourcePath(resPath);
    }
 
    public PSvgNode(URI svgURI) {
        setSvgURI(svgURI);
    }
 
 
 
    /**
     * @return the uni of the document being displayed by this icon
     */
    public URI getSvgURI() {
        return svgURI;
    }
 
    /**
     * Loads an SVG document from a URI.
     *
     * @param svgURI - URI to load document from
     */
    public void setSvgURI(URI svgURI) {
        URI old = svgURI;
        this.svgURI = svgURI;
        if (svgURI != null) {
            SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
            if (diagram == null)
                setBounds(0, 0, 0, 0);
            else
                setBounds(0, 0, diagram.getWidth(), diagram.getWidth());
            invalidatePaint();
        }
 
        firePropertyChange(PROPERTY_CODE_IMAGE, PROPERTY_IMAGE, old, this.svgURI);
    }
 
    /**
     * Loads an SVG document from the classpath.  This function is equivilant to
     * setSvgURI(new URI(getClass().getResource(resourcePath).toString());
     *
     * @param resourcePath - resource to load
     */
    public void setSvgResourcePath(String resourcePath) {
        try {
            setSvgURI(getClass().getResource(resourcePath).toURI());
        }
        catch (Exception e) {
            throw new RuntimeException("Resource not found "+resourcePath,e);
        }
    }
 
 
    protected void paint(PPaintContext paintContext) {
 
        SVGDiagram diagram = svgUniverse.getDiagram(svgURI);
        if (diagram == null) return;
        double iw = diagram.getWidth();
        double ih = diagram.getHeight();
        Graphics2D g = paintContext.getGraphics();
        PBounds b = getBoundsReference();
        try {
            if (b.x != 0 || b.y != 0 || b.width != iw || b.height != ih) {
                g.translate(b.x, b.y);
                g.scale(b.width / iw, b.height / ih);
                diagram.render(g);
                g.scale(iw / b.width, ih / b.height);
                g.translate(-b.x, -b.y);
            } else {
                diagram.render(g);
            }
        } catch (SVGException e) {
            throw new RuntimeException(e);
        }
    }
 
    //testing method
    public static void main(String[] args){
        new PFrame(){
 
 
 
            public void initialize() {
                setSize(1000,1000);
                getCanvas().setBackground(Color.blue);
                //add your own svg resource
                PSvgNode n = new PSvgNode("/org/asterope/map/img/star.svg");
 
                getCanvas().getCamera().addChild(n);
                n.setOffset(500,500);
                n.animateToPositionScaleRotation(10,10,10,10,10000);
            }
        };
    }
 
}
· 2009/11/07 · Jan Kotek

Older entries >>