[VTM] Trying to use a PolygonBucket to render custom polygons (failed)

199 views
Skip to first unread message

Rodo Channing

unread,
Sep 7, 2016, 11:28:11 PM9/7/16
to mapsforge-dev
Hi!

First of all, thanks a lot for such awesome libraries. It's truly humbling to take a look at complex code and realise how much more I need to learn despite working for more than 20 years in the programming industry.

Long story short:

- I need to add custom polygons to outline custom areas, just like markers. For markers, I use a PathLayer layer.
- So after studying PathLayer source code, I decided to change it slightly in order to use PolygonBuckets instead of LineBuckets.
- It doesn't work :( Been like debugging step by step, opengl and all, for a week. And I don't have a clue why it fails.

But I think I'm pretty close:
  • The polygon bucket is created and added to the buckets list.
  • The projection points look valid and in range
  • The PolygonRenderer is called by the BucketRenderer, the flow looks apparently correct (it passes validations, gl_****.... functions and specifically gl_drawarrays is called at the end)
  • Also added a normal VectorLayer (OsmLandUse) together with my PolygonLayer. Checked the polygons generated have the same range of coordinates as the land use areas, a similar (copied) areastyle (even though I don't understand most parameters like fadescale/blendscale), ... but the OsmLandUse polygons generated by the datasource do render perfectly and mines don't. :(
Can anybody please take a look and give me any very-welcome advice? I'm so frustrated and ran out of ideas. Find enclosed file GeoShapesLayer.java, which is just like PathLayer.java, only with a TYPE parameter that allows to create Polygon buckets i
 

package org.oscim.layers;

import android.util.Log;

import org.oscim.backend.canvas.Paint.Cap;
import org.oscim.core.Box;
import org.oscim.core.GeoPoint;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.MapPosition;
import org.oscim.core.MercatorProjection;
import org.oscim.core.Tile;
import org.oscim.map.Map;
import org.oscim.renderer.BucketRenderer;
import org.oscim.renderer.GLViewport;
import org.oscim.renderer.bucket.LineBucket;
import org.oscim.renderer.bucket.MeshBucket;
import org.oscim.renderer.bucket.PolygonBucket;
import org.oscim.renderer.bucket.RenderBucket;
import org.oscim.renderer.bucket.RenderBuckets;
import org.oscim.theme.styles.AreaStyle;
import org.oscim.theme.styles.LineStyle;
import org.oscim.theme.styles.RenderStyle;
import org.oscim.utils.FastMath;
import org.oscim.utils.async.SimpleWorker;
import org.oscim.utils.geom.LineClipper;

import java.util.ArrayList;
import java.util.List;

import tv.nebular.tribes.config.Conf;

/**
 * A Layer that allows to add lines or polygons. Based on PathLayer. Changes are only in the Worker section
 * where a PolygonBucket is created rather than a LineBucket if the layer is built for polygons
 * (parameter mode in constructor with the constants MODE_XXXX
 */

@SuppressWarnings("unused")
public class GeoShapesLayer extends Layer {

   public final static int MODE_LINE = 0, MODE_POLYGON = 1;
   private static final String TAG = "GeoShapesLayer";

   int mShapeMode;

   final ArrayList<GeoPoint> mPoints;
   boolean mUpdatePoints;
   LineStyle mLineStyle;

   RenderStyle mAreaStyle;
   GeoShapesLayer.Worker mWorker;
   GeometryBuffer mGeom;

   public GeoShapesLayer(Map map, int mode, LineStyle style, RenderStyle areaStyle) {

      super(map);

      mLineStyle = style;
      mPoints = new ArrayList<>();
      mRenderer = new RenderPolygon();
      mWorker = new GeoShapesLayer.Worker(map);
      mAreaStyle = areaStyle;
      mShapeMode = mode;

      if (Conf.LOG_ON) Log.v(TAG, "Creating Shapes Layer for mode " + mode);

   }

   public void clearPath() {
      if (!mPoints.isEmpty()) {
         synchronized (mPoints) {
            mPoints.clear();
         }

         updatePoints();
      }
   }

   public void setPoints(List<GeoPoint> pts) {
      synchronized (mPoints) {
         mPoints.clear();
         mPoints.addAll(pts);
      }

      updatePoints();
   }

   public void addPoint(GeoPoint pt) {
      synchronized (mPoints) {
         mPoints.add(pt);
      }

      updatePoints();
   }

   public void addPoint(int latitudeE6, int longitudeE6) {
      synchronized (mPoints) {
         mPoints.add(new GeoPoint(latitudeE6, longitudeE6));
      }

      updatePoints();
   }

   private void updatePoints() {
      mWorker.submit(10L);
      mUpdatePoints = true;
      if (Conf.LOG_ON) Log.v(TAG, "Points updated, total "+mPoints.size());

   }

   public List<GeoPoint> getPoints() {
      return mPoints;
   }



   final class Worker extends SimpleWorker<GeoShapesLayer.Task> {
      private final int max = 2048;
      private static final int MIN_DIST = 3;
      private double[] mPreprojected = new double[2];
      private float[] mPPoints = new float[0];
      private final LineClipper mClipper = new LineClipper(-2048.0F, -2048.0F, 2048.0F, 2048.0F);
      private int mNumPoints;

      public Worker(Map map) {
         super(map, 0L, new GeoShapesLayer.Task(), new GeoShapesLayer.Task());
      }

      public boolean doWork(GeoShapesLayer.Task task) {

         int size = mNumPoints;

         if (mUpdatePoints) {
            ArrayList ll = mPoints;

            synchronized (mPoints) {
               mUpdatePoints = false;
               mNumPoints = size = mPoints.size();
               ArrayList zoomlevel = mPoints;
               double[] mx = mPreprojected;
               if (size * 2 >= mx.length) {
                  mx = mPreprojected = new double[size * 2];
                  mPPoints = new float[size * 2];
               }

               for (int i = 0; i < size; ++i) {
                  MercatorProjection.project((GeoPoint) zoomlevel.get(i), mx, i);
                  if (Conf.LOG_ON) Log.v(TAG, "- Point "+i+" result "+mx[i]+","+mx[i+1]+" zoom "+zoomlevel.get(i));
               }
            }

         } else if (mGeom != null) {

            GeometryBuffer geometryBuffer = mGeom;
            mGeom = null;

            size = geometryBuffer.index[0];
            double[] preprojected = mPreprojected;
            if (size > preprojected.length) {
               preprojected = mPreprojected = new double[size * 2];
               mPPoints = new float[size * 2];
            }

            for (int i = 0; i < size; i += 2) {
               MercatorProjection.project((double) geometryBuffer.points[i + 1], (double) geometryBuffer.points[i], preprojected, i >> 1);
            }

            mNumPoints = size >>= 1;
         }

         // preprojected buffer now created

         if (size == 0) {
            if (task.bucket.get() != null) {
               task.bucket.clear();
               mMap.render();
            }

            return true;
         } else {

            RenderBucket bucket;

            switch (mShapeMode) {
               case MODE_MESH:
                  bucket = task.bucket.getMeshBucket(0);
                  ((MeshBucket) bucket).area = (AreaStyle) mAreaStyle;
                  break;
               case MODE_POLYGON:
                  bucket = task.bucket.getPolygonBucket(0);
                  ((PolygonBucket) bucket).area = (AreaStyle) mAreaStyle;
                  break;
               case MODE_LINE:
               default:

                  if (mLineStyle.stipple == 0 && mLineStyle.texture == null) {
                     bucket = task.bucket.getLineBucket(0);
                  } else {
                     bucket = task.bucket.getLineTexBucket(0);
                  }
                  ((LineBucket) bucket).line = mLineStyle;
                  break;
            }

            mMap.getMapPosition(task.pos);
            int zoom = task.pos.zoomLevel;
            task.pos.scale = (double) (1 << zoom);
            double mx = task.pos.x;
            double my = task.pos.y;
            double scale = (double) Tile.SIZE * task.pos.scale;
            byte flip = 0;
            int maxx = Tile.SIZE << zoom - 1;

            // x,y son las coordenadas en el MAPA del punto con respecto al CENTRO DEL PUTISIMO MAPA.
            int x = (int) ((mPreprojected[0] - mx) * scale);
            int y = (int) ((mPreprojected[1] - my) * scale);

            Box box = new Box();
            mMap.viewport().getBBox(box, 0);
            box.scale(scale);

            if (Conf.LOG_ON) Log.v(TAG, "- Visible Map: from "+((int)(box.xmin))+","+((int)(box.ymin*scale))+" to "+((int)(box.xmax))+","+((int)(box.xmax))+" TAMAÑO "+((int)(box.getWidth()))+"x"+((int)(box.getHeight())));
            if (Conf.LOG_ON) Log.v(TAG, "- Offset obj r/center "+x+","+y);

            if (x > maxx) {
               x -= maxx * 2;
               flip = -1;
            } else if (x < -maxx) {
               x += maxx * 2;
               flip = 1;
            }

            // x,y now contain absolute tile coordinates of the poing

            float[] projected = mPPoints;

            int index = addPoint(projected, 0, x, y);

            float prevX = (float) x;
            float prevY = (float) y;
            float[] segment = null;

            switch (mShapeMode) {

               case MODE_POLYGON:

                  for (int j = 2; j < size * 2; j += 2) {

                     x = (int) ((mPreprojected[j] - mx) * scale);
                     y = (int) ((mPreprojected[j + 1] - my) * scale);

                     projected[index++] = (float) x;
                     projected[index++] = (float) y;

                  }

                  GeometryBuffer polygon = new GeometryBuffer();
                  polygon.startPolygon();

                  for (int i = 0, l = projected.length; i < l; i += 2) {
                     polygon.addPoint(projected[i], projected[i + 1]);
                  }

                  if (Conf.LOG_ON) Log.v(TAG, "Created polygon " + polygon);

                  ((PolygonBucket) bucket).addPolygon(polygon);

                  break;

               case MODE_MESH:

                  for (int j = 2; j < size * 2; j += 2) {

                     x = (int) ((mPreprojected[j] - mx) * scale);
                     y = (int) ((mPreprojected[j + 1] - my) * scale);

                     projected[index++] = (float) x;
                     projected[index++] = (float) y;

                  }

                  polygon = new GeometryBuffer();
                  polygon.startPolygon();

                  for (int i = 0, l = projected.length; i < l; i += 2) {
                     polygon.addPoint(projected[i], projected[i + 1]);
                  }

                  ((MeshBucket) bucket).addMesh(polygon);
                  break;

               case MODE_LINE:

                  boolean isClosed = false;
                  mClipper.clipStart((float) x, (float) y);

                  for (int j = 2; j < size * 2; j += 2) {
                     x = (int) ((mPreprojected[j] - mx) * scale);
                     y = (int) ((mPreprojected[j + 1] - my) * scale);
                     byte flipDirection = 0;
                     if (x > maxx) {
                        x -= maxx * 2;
                        flipDirection = -1;
                     } else if (x < -maxx) {
                        x += maxx * 2;
                        flipDirection = 1;
                     }

                     if (flip != flipDirection) {

                        flip = flipDirection;
                        if (index > 2) {
                           ((LineBucket) bucket).addLine(projected, index, isClosed);
                        }

                        mClipper.clipStart((float) x, (float) y);
                        index = addPoint(projected, 0, x, y);

                     } else {
                        int clip = mClipper.clipNext((float) x, (float) y);

                        if (clip < 1) {
                           if (index > 2) {
                              ((LineBucket) bucket).addLine(projected, index, isClosed);
                           }

                           if (clip < 0) {
                              segment = mClipper.getLine(segment, 0);
                              ((LineBucket) bucket).addLine(segment, 4, isClosed);
                              prevX = mClipper.outX2;
                              prevY = mClipper.outY2;
                           }

                           index = 0;
                        } else {

                           float  dx = (float) x - prevX,
                                 dy = (float) y - prevY;
                           // add point if far enough (3px)
                           if (index == 0 || FastMath.absMaxCmp(dx, dy, 3.0F)) {
                              projected[index++] = prevX = (float) x;
                              projected[index++] = prevY = (float) y;
                              if (Conf.LOG_ON) Log.v(TAG, "- Projected point "+((index/2)-1)+" is "+x+","+y+" scale "+scale);
                           }
                        }
                     }
                  }

                  if (index > 2) {
                     ((LineBucket) bucket).addLine(projected, index, isClosed);
                  }
            }

            mMap.render();
            return true;
         }

      }

      public void cleanup(GeoShapesLayer.Task task) {
         task.bucket.clear();
      }

      private int addPoint(float[] points, int i, int x, int y) {
         points[i++] = (float) x;
         points[i++] = (float) y;
         return i;
      }
   }

   static final class Task {
      RenderBuckets bucket = new RenderBuckets();
      MapPosition pos = new MapPosition();
   }

   final class RenderPolygon extends BucketRenderer {
      private int mCurX = -1;
      private int mCurY = -1;
      private int mCurZ = -1;


      public synchronized void update(GLViewport v) {
         int tz = 1 << v.pos.zoomLevel;
         int tx = (int) (v.pos.x * (double) tz);
         int ty = (int) (v.pos.y * (double) tz);
         if (tx != this.mCurX || ty != this.mCurY || tz != this.mCurZ) {
            mWorker.submit(100L);
            mCurX = tx;
            mCurY = ty;
            mCurZ = tz;
         }

         GeoShapesLayer.Task t = mWorker.poll();
         if (t != null) {
            mMapPosition.copy(t.pos);

            this.buckets.set(t.bucket.get());
            this.compile();
         }
      }
   }
}


Emux

unread,
Sep 8, 2016, 2:46:24 AM9/8/16
to mapsfo...@googlegroups.com
I recommend advancing to latest VTM fork, it's where development takes place now and it has been improved vastly in too many areas.

We have 2 PathLayer classes:
- The regular one in 'vtm' module using the normal API, but with issues probably due to LineClipper (see #108)
- The JTS one in 'vtm-jts' module using also the JTS library

'vtm-jts' module supports all geometries, check VectorLayerMapActivity in vtm-android-example sample app drawing points with circle buffer.
Using a PolygonDrawable you can overlay polygons.

--
Emux

Rodo Channing

unread,
Sep 12, 2016, 8:07:38 PM9/12/16
to mapsforge-dev
Hi Emux and thanks for the reply.

I upgraded to your fork, found the functionality and wow! the improvements all over the code are awesome.

Wrote an issue on GitHub with an enhacement request ):

Keep up the good work.


Reply all
Reply to author
Forward
0 new messages