Archives for: December 2008

12/31/08

Permalink 05:56:48 pm, by admin Email , 192 words, 156 views   English (US)
Categories: Java

Adventures into Nasa Worldwind (4)

Distances and globes… There must be signposts pointing to far-away locations all over the world. Those signposts probably list the distances as a bird would fly, but that’s not the only to measure a distance between two points. One could also use a route planner, or use the straight line from A to B, which would go through the earth.

The following snippet calculates the distance between two points in two ways: direct, as well as following the great circle (a circle that follows the earth in such a way that it results in the shortest path between the points while following the curvature of the earth). Note that this doesn’t account for elevations.

Code:

private void calcAndPrintDistance(Model model) throws Exception {
        Position amsterdam = Position.fromDegrees(52.35, 4.917, 0.0);
        Position paris = Position.fromDegrees(48.87, 2.33, 0.0);
 
        Vec4 pointA = model.getGlobe().computePointFromPosition(amsterdam);
        Vec4 pointB = model.getGlobe().computePointFromPosition(paris);
        System.out.printf("Direct Amsterdam - Paris = ~ %.01f km\n", pointA.distanceTo3(pointB)/1000.0);
 
        double circleDist = LatLon.ellipsoidalDistance(
                amsterdam.getLatLon(),
                paris.getLatLon(), model.getGlobe().getEquatorialRadius(),
                model.getGlobe().getPolarRadius());
 
        System.out.printf("Great circle distance Amsterdam - Paris = ~ %.01f km\n", circleDist/1000.0);
    }

To obtain the necessary geometry, supply an instance of gov.nasa.worldwind.Model.

12/23/08

Permalink 04:45:56 pm, by admin Email , 300 words, 410 views   English (US)
Categories: General

Adventures into Nasa Worldwind (3)

Showing icons on the map is done by adding a IconLayer to the map. Icons can then be added to this layer. You can provide your own images to be shown. These images are then shown with their center-bottom at the given position. Additionally, you can provide a pedestal: an additional icon that is used as a pointer (and is the same for all icons on the layer). An offset allows the icon to be offset in a vertical direction from the pedestal.

The following shows icons for Amsterdam and Paris. The marker for Amsterdam is given a tooltip (that is always visible this way). The triangle is the pedestal, the circle the icon.

Code:

package gpxeditor;
 
import gov.nasa.worldwind.Model;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.awt.WorldWindowGLCanvas;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.layers.IconLayer;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.render.Pedestal;
import gov.nasa.worldwind.render.UserFacingIcon;
import java.awt.BorderLayout;
import java.awt.Component;
import javax.swing.JFrame;
 
public class Test {
    private WorldWindowGLCanvas worldWindCanvas;
    private Model model;
 
    public static void main(String[] args) throws Exception {
        new Test();
    }
 
    private Test() throws Exception {
        JFrame frame = new JFrame();
        frame.setLayout(new BorderLayout());
        frame.add(createWorldWindComponent(), BorderLayout.CENTER);
        model.getLayers().add(createIconLayer());
        frame.setSize(600, 500);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
 
    private Component createWorldWindComponent() {
        worldWindCanvas = new WorldWindowGLCanvas();
        model = (Model)WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
        worldWindCanvas.setModel(model);
        return worldWindCanvas;
    }
 
    private Layer createIconLayer() throws Exception {
        IconLayer infoLayer = new IconLayer();
        UserFacingIcon marker = new UserFacingIcon("locationicon.png", Position.fromDegrees(52.35, 4.917, 0.0));
        marker.setToolTipText("Amsterdam tooltip");
        marker.setShowToolTip(true);
        infoLayer.addIcon(marker);
        marker = new UserFacingIcon("locationicon.png", Position.fromDegrees(48.87, 2.33, 0.0));
        infoLayer.addIcon(marker);
        infoLayer.setPedestal(new Pedestal("locationpedestal.png", Position.ZERO));
        infoLayer.getPedestal().setSpacingPixels(2);
        return infoLayer;
 
    }
 
}

12/20/08

Permalink 04:23:22 pm, by admin Email , 818 words, 386 views   English (US)
Categories: Java

Adventures into Nasa Worldwind (2)

One of the things that I’ve wanted to do with WorldWind is display tracks from my holidays where I recorded a GPS-track. Doing so is rather easy (one can directly use a GPX format track, no need to convert it like with Google Earth), but be aware of some caveats.

WorldWind makes loading GPX files easy with just a call to a new GpxReader-instance. The result is a collection of tracks. These tracks can be directly fed to a TrackPipesLayer:

Code:

import gov.nasa.worldwind.Model;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.awt.WorldWindowGLCanvas;
import gov.nasa.worldwind.formats.gpx.GpxReader;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.layers.TrackPipesLayer;
import gov.nasa.worldwind.tracks.Track;
import java.awt.BorderLayout;
import java.awt.Component;
import java.util.List;
import javax.swing.JFrame;
 
public class Test {
    private WorldWindowGLCanvas worldWindCanvas;
    private Model model;
 
    public static void main(String[] args) throws Exception {
        new Test();
    }
 
    private Test() throws Exception {
        JFrame frame = new JFrame();
        frame.setLayout(new BorderLayout());
        frame.add(createWorldWindComponent(), BorderLayout.CENTER);
        model.getLayers().add(createTrackLayer());
        frame.setSize(600, 500);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
 
    private Component createWorldWindComponent() {
        worldWindCanvas = new WorldWindowGLCanvas();
        model = (Model)WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
        worldWindCanvas.setModel(model);
        return worldWindCanvas;
    }
 
    private Layer createTrackLayer() throws Exception {
        GpxReader reader = new GpxReader();
        reader.readFile("myfile.gpx");
        List<Track> tracks = reader.getTracks();
        return new TrackPipesLayer(tracks);
    }
}

This produces a somewhat unsatisfactory result: while the layer looks nice with the white lines and red points, it is slow for larger tracks and the size of the objects is so big that it obscures the track itself:

To change the diameter of the pipes, you need to pull some WorldWind code to your own codebase and modify the diameter settings (they are there, but not mutable). In the end that resulted in a mess that didn’t perform, and pipes disappeared underneath the earth where there was a discrepancy between the elevation model in WorldWind and the elevations obtained from the GPS.

Better results are obtained through the RenderableLayer:

Code:

import gov.nasa.worldwind.Model;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.awt.WorldWindowGLCanvas;
import gov.nasa.worldwind.formats.gpx.GpxReader;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.layers.Layer;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.render.Polyline;
import gov.nasa.worldwind.tracks.Track;
import gov.nasa.worldwind.tracks.TrackPoint;
import gov.nasa.worldwind.tracks.TrackSegment;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JFrame;
 
public class Test {
    private WorldWindowGLCanvas worldWindCanvas;
    private Model model;
 
    public static void main(String[] args) throws Exception {
        new Test();
    }
 
    private Test() throws Exception {
        JFrame frame = new JFrame();
        frame.setLayout(new BorderLayout());
        frame.add(createWorldWindComponent(), BorderLayout.CENTER);
        model.getLayers().add(createTrackLayer());
        frame.setSize(600, 500);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
 
    private Component createWorldWindComponent() {
        worldWindCanvas = new WorldWindowGLCanvas();
        model = (Model)WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
        worldWindCanvas.setModel(model);
        return worldWindCanvas;
    }
 
    private Layer createTrackLayer() throws Exception {
        final RenderableLayer layer = new RenderableLayer();
        for (TrackSegment segment : getSegments()) {
            Polyline trackLine = new Polyline(new TrackToPositionIterable(segment));
            trackLine.setFollowTerrain(true);
            trackLine.setColor(Color.RED);
            trackLine.setAntiAliasHint(Polyline.ANTIALIAS_NICEST);
            trackLine.setLineWidth(2.5);
            layer.addRenderable(trackLine);
        }
        return layer;
    }
 
    private List<TrackSegment> getSegments() throws Exception {
        List<TrackSegment> segments = new ArrayList<TrackSegment>();
        GpxReader reader = new GpxReader();
        reader.readFile("myfile.gpx");
        List<Track> tracks = reader.getTracks();
        for (Track t : tracks) {
            for (TrackSegment ts : t.getSegments()) {
                segments.add(ts);
            }
        }
        return segments;
    }
 
    private static class TrackToPositionIterable implements Iterable<Position> {
        private final TrackSegment t;
        public TrackToPositionIterable(TrackSegment t) {
            this.t = t;
        }
        public Iterator<Position> iterator() {
            return new Iterator<Position>() {
                Iterator<TrackPoint> trackIt = t.getPoints().iterator();
 
                public boolean hasNext() {
                    return trackIt.hasNext();
                }
 
                public Position next() {
                    return trackIt.next().getPosition();
                }
 
                public void remove() {
                    throw new UnsupportedOperationException();
                }
            };
        }
    }
}

The code is a little bit more involved. When the layer is created, we iterate over all TrackSegments (each track can be made up of multiple segments). Each of these segments is transformed into a Polyline. To do so, we need to supply an Iterable<Position> instance. The TrackToPositionIterable class does exactly that.

The downside of this solution is that GPS height information isn’t used on the map. The result is somewhat faster though, and the track is sticking to the surface, overall providing a more usable result.

In case you’d want to modify the track data: changes to the TrackSegment objects seem not to be propagated to the Polylines. The iterator is used only once, during construction. Therefore you’d need to remove the Polyline, construct a new one and add it. Additionally, you’d need to make a call similar to “layer.firePropertyChange(AVKey.LAYER, null, layer);” to trigger an update of the screen.

12/16/08

Permalink 05:08:38 pm, by admin Email , 471 words, 166 views   English (US)
Categories: Java

Adventures into Nasa Worldwind (1)

The time of year has come where I find myself having some time to play around with applications and frameworks that have been on the list for some time. One of these is Nasa’s WorldWind: an application somewhat similar to Google Earth. There are several versions: one in C# which seems to be just the application, while the other is a Java library that allows one to embed maps in your own application.

This turns out to be really easy:

Code:

import gov.nasa.worldwind.Model;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.awt.WorldWindowGLCanvas;
import gov.nasa.worldwind.layers.Layer;
import java.awt.BorderLayout;
import java.awt.Component;
import javax.swing.JFrame;
 
public class Test {
    private WorldWindowGLCanvas worldWindCanvas;
    private Model model;
 
    public static void main(String[] args) {
        new Test();
    }
 
    private Test() {
        JFrame frame = new JFrame();
        frame.setLayout(new BorderLayout());
        frame.add(createWorldWindComponent(), BorderLayout.CENTER);
        frame.setSize(600, 500);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
        showLayers();
    }
 
    private Component createWorldWindComponent() {
        worldWindCanvas = new WorldWindowGLCanvas();
        model = (Model)WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
        worldWindCanvas.setModel(model);
        return worldWindCanvas;
    }
 
    private void showLayers() {
        for (Layer layer : model.getLayers()) {
            System.out.printf(
                    "%-30s | %.01f | %.01f | %-5b | %-5b | %-5b | %-5b\n",
                    layer.getName(),
                    layer.getOpacity(),
                    layer.getScale(),
                    layer.isAtMaxResolution(),
                    layer.isEnabled(),
                    layer.isMultiResolution(),
                    layer.isPickEnabled());
        }
    }
}

The actual creation happens inside the createWorldWindComponent() method. A new canvas is created, initiated with a default model, and off you go!

The default model applies some layers to the globe. These layers are iterated over in the showLayers() method. This results in:

Stars                          | 1.0 | 1.0 | true  | true  | false | true 
Atmosphere                     | 1.0 | 1.0 | true  | true  | false | true 
Fog                            | 1.0 | 1.0 | true  | true  | false | true 
NASA Blue Marble Image         | 1.0 | 1.0 | true  | true  | false | false
BlueMarble (WMS) 05/2004       | 1.0 | 1.0 | false | true  | true  | false
i-cubed Landsat                | 1.0 | 1.0 | false | true  | true  | false
NAIP California                | 1.0 | 1.0 | false | true  | true  | false
USGS Urban Area Ortho          | 1.0 | 1.0 | false | true  | true  | false
Place Names                    | 1.0 | 1.0 | true  | true  | false | true 
World Map                      | 0.6 | 1.0 | true  | true  | false | true 
Scale bar                      | 1.0 | 1.0 | true  | true  | false | true 
Compass                        | 0.8 | 1.0 | true  | true  | false | true 

That’s quite a few layers! Stars, atmosphere and fog create a bit of atmosphere, while ‘NASA Blue Marble Image’, ‘BlueMarble (WMS) 05/2004′, ‘i-cubed Landsat’, ‘NAIP California’ and ‘USGS Urban Area Ortho’ provide the map data. Note that the data is somewhat older, and especially outside the USA isn’t too detailed.
The place names layer displays names over the map. The ‘World Map’, ‘Scale bar’ and ‘Compass’ layers draw the widgets on top of the screen that show an overall world map, a bar indicating the scale and a compass. The latter is hardly necessary as world wind keeps North at the top of the screen (in contrast to Google Earth), unless you make some movements around the poles in which case it will keep North at the bottom.

Over the next few days/weeks I’ll post some more experiments with WorldWind.

12/02/08

Permalink 09:56:13 pm, by admin Email , 1047 words, 141 views   English (US)
Categories: Java

Fork/Join Framework

The Fork/Join framework is the latest extension on JSR 166 (the concurrency utilities added in Java 5). This extension adds a new framework to allow developers to easily include fine-grained parallelization in their applications. Doug Lea hopes this will help developing software for massive multi-core machines.

The framework consists, broadly speaking, of two parts: the fork/join framework and the ParallelArray. The fork/join framework allows for flexible use, while ParallelArray is built on top of the fork/join framework and intends to make common cases simple.

The idea underlying the fork/join framework is that many tasks can be decomposed into smaller tasks, of which the results can be merged afterwards. Often these decomposed tasks can be further decomposed recursively. Of course, to allow this, one sometimes needs to chose a different algorithm that the one that is most efficient on a single thread. Compare quicksort and mergesort: The former is computationally more efficient, while the latter is better to parallelize and might therefore return results in less time, given enough cores to perform the computation.

The following is an example of using the fork/join framework:

Code:

import javax.time.calendar.LocalDate;
import javax.time.calendar.format.DateTimeFormatter;
import javax.time.calendar.format.DateTimeFormatters;
import jsr166y.forkjoin.ForkJoinPool;
import jsr166y.forkjoin.ForkJoinTask;
import jsr166y.forkjoin.RecursiveTask;
 
public class FJMain {
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool(5);
        
        String[] data = generateData();
        ForkJoinTask<LocalDate> task = new DateSearcher(data, 0, data.length);
        LocalDate result = pool.invoke(task);
        System.out.println(result);
    }
    
    private static class DateSearcher extends RecursiveTask<LocalDate> {
        private static final int THRESHOLD = 5;
        private final DateTimeFormatter formatter;
        private final String[] input;
        private final int rangeStart;
        private final int rangeEnd;
        
        public DateSearcher(String[] input, int rangeStart, int rangeEnd) {
            formatter = DateTimeFormatters.isoDate();
            this.input = input;
            this.rangeEnd = rangeEnd;
            this.rangeStart = rangeStart;
        }
 
        @Override
        protected LocalDate compute() {
            LocalDate max = null;
            if (rangeEnd - rangeStart < THRESHOLD) {
                for (int i = rangeStart; i < rangeEnd; i++) {
                    LocalDate ld = formatter.parse(input[i]).toLocalDate();
                    if (max == null || ld.isAfter(max)) {
                        max = ld;
                    }
                }
                return max;
            } else {
                int middle = (rangeStart + rangeEnd) / 2;
                DateSearcher a = new DateSearcher(input, rangeStart, middle);
                DateSearcher b = new DateSearcher(input, middle, rangeEnd);
                a.fork();
                LocalDate resultB = b.forkJoin();
                LocalDate resultA = a.join();
                if (resultA.isAfter(resultB)) {
                    max = resultA;
                } else {
                    max = resultB;
                }
                return max;
            }
        }
    }
    
    private static String[] generateData() {
        String[] result = new String[20000];
        
        for (int i = 0; i < result.length; i++) {
            result[i] = String.format("%04d-%02d-%02d",
                                        (int)(1900+100*Math.random()),
                                        (int)(1+12*Math.random()),
                                        (int)(1+25*Math.random()));
        }
        return result;
    }
 
}

The program generates some strings representing dates, and creates a new instance of DateSearcher passing all data plus the boundaries. This DateSearcher is a RecursiveTask, which provides the outline for tasks that will be decomposed recursively and return a value. The compute method checks amount of work to be done by looking at the boundary values. The reason to do so is that at a certain threshold it is more efficient to go over the element sequentially than breaking the sequence down even further.
If the task is still too big, however, the range is split in two and each is given to a new DateSearcher instance. The first is handed to the forkjoin-pool-workers with a call to fork, while the second is also passed to the pool, but using forkJoin(): this makes the current task wait for the result to return and is supposedly more efficient. When the result is obtained, the task starts waiting for the result from the first task, and when both results are in, the task returns its result.
The threshold in the above was picked at random. In reality one will want to perform some experiments. Some research by Brian Goetz has shown that the exact numbers are not too significant: difference of an order of magnitude still perform relatively well.

The second part is the ParallelArray implementation. This is meant to make things even easier, and should be more efficient than most people would be able to using the fork/join framework. It also allows one to do so without thinking about recursive decomposition, and without optimizing thresholds!
The ParallelArray is based around regular arrays onto which operations can be applied, or on which mappings, filters and reductions can be used. To really appreciate it one just needs to play with it. But some guidelines to its use are:

  • Filters must come before mappings. This is primarily to allow one to be able to estimate the performance of the expression.
  • If you need a filter after a mapping, just finish the mapping with all() to create an intermediate ParallelArray
  • The actual work is only performed in the final step of the expression (on a call to methods like all(), apply(), max(), etc.

The following is an adaptation from the JavaDoc. It determines the maximum score for this year’s (fictional) students:

Code:

import jsr166y.forkjoin.Ops;
import jsr166y.forkjoin.ParallelArray;
import jsr166y.forkjoin.ParallelDoubleArray;
 
 
public class PAMain {
    public static void main(String[] args) {
        ParallelArray<Student> students =
                ParallelArray.createFromCopy(
                    generateData(),
                    ParallelArray.defaultExecutor());
        double bestScore = students.withFilter(thisYearsStudent).withMapping(selectScore).max();
        System.out.println(bestScore);
    }
    
    private static Student[] generateData() {
        return new Student[] {
            new Student("A", 2001, 2.0),
            new Student("B", 2002, 2.0),
            new Student("C", 2004, 2.0),
            new Student("D", 2008, 2.0),
            new Student("E", 2001, 2.0),
            new Student("F", 2002, 4.0),
            new Student("G", 2004, 4.0),
            new Student("H", 2008, 4.0),
            new Student("I", 2001, 4.0),
            new Student("J", 2002, 4.0),
            new Student("K", 2004, 8.0),
            new Student("L", 2008, 8.0),
            new Student("M", 2001, 8.0),
            new Student("N", 2002, 8.0),
            new Student("O", 2004, 8.0),
            new Student("P", 2008, 16.0),
            new Student("Q", 2001, 16.0),
            new Student("R", 2002, 16.0),
            new Student("S", 2004, 16.0),
            new Student("T", 2008, 16.0),
            new Student("U", 2001, 32.0),
            new Student("V", 2002, 32.0),
            new Student("W", 2004, 32.0),
            new Student("X", 2008, 32.0),
            new Student("Y", 2001, 32.0),
            new Student("Z", 2002, 64.0),
        };
    }
    
    public static class Student {
        final String name;
        final int graduationYear;
        final double score;
        
        public Student(String name, int graduationYear, double score) {
            this.name = name;
            this.graduationYear = graduationYear;
            this.score = score;
        }
    }
 
    static final Ops.Predicate<Student> thisYearsStudent =
        new Ops.Predicate<Student>() {
            public boolean op(Student s) {
                return s.graduationYear == 2008;
            }
        };
 
    static final Ops.ObjectToDouble<Student> selectScore =
        new Ops.ObjectToDouble<Student>() {
            public double op(Student student) {
                return student.score;
            }
        };
 
}

References:
Concurrency JSR-166 Interest Site - Information straight from Doug Lea
Stick a fork in it, Part 1 - article by Brian Goetz (make sure you read the second part, too)
JavaOne session TS-5515 - the JavaOne session primarilly on the Fork/Join framework

Floris' Blog

Personal blog on my interests.

December 2008
Sun Mon Tue Wed Thu Fri Sat
 << < Current> >>
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31      

Search

Categories

Misc

XML Feeds

What is RSS?

Who's Online?

  • Guest Users: 4

powered by b2evolution free blog software