antoniocarlon / richmaps

Advanced rendering options for polygons and polylines on Google Maps Android API v2
Apache License 2.0
39 stars 7 forks source link

RichLayer placement not factoring in map padding #3

Closed brwskitime closed 7 years ago

brwskitime commented 7 years ago

Compared to the standard google maps Polyline, the RichPolyline doesn't line up. This can be seen by adding a google map Marker.

brwskitime commented 7 years ago

Looks like it's always shifted in the same direction, towards the y coordinate being too low.

antoniocarlon commented 7 years ago

I have tested it on several locations (it could be a latitude issue so I tested it from lalitude 0.23 to latitude 40) and the marker I set is always precisely located in a vertex of the polyline. Could you share the code that you are using to create your RichPolyline?

brwskitime commented 7 years ago

Sure. Here's the class I created wrapping the functionality.

public class SpeedGradientPolyline implements IPolyline {

    public static final double LOW_SPEED_CLAMP_MpS = 0;
    public static final int[] COLORS = {Color.rgb(47,163,0),Color.rgb(247,255,29), Color.rgb(248,78,0)};
    private double HIGH_SPEED_CLAMP_KMpH = 95.5; // 60 miles/h
    private double HIGH_SPEED_CLAMP_MpS = HIGH_SPEED_CLAMP_KMpH * 1000 / (60 * 60);

    protected GoogleMap mMap;
    protected RichLayer richLayer;
    protected RichPolyline polyline;

    public SpeedGradientPolyline(View mapContainer, GoogleMap googleMap) {
        this(mapContainer,googleMap,0);
    }

    public SpeedGradientPolyline(View mapContainer, GoogleMap googleMap, float lineWidth) {
        mMap = googleMap;
        richLayer = new RichLayer.Builder(mapContainer,googleMap).zIndex(0).build();

        RichPolylineOptions polylineOptions = new RichPolylineOptions(null)
                .zIndex(1)
                .strokeWidth((int)lineWidth)
                .strokeColor(COLORS[0]) // the base color
                .linearGradient(true);

        polyline = polylineOptions.build();
        richLayer.addShape(polyline);
    }

    @Override
    public void addPoint(Location location) {
        addPoint(location,true);
    }

    private void addPoint(Location location, boolean refresh) {
        if (location == null || !location.hasSpeed()) return;
        float prop = getSpeedProportion(location.getSpeed());
        int color = interpolateColor(prop);
        polyline.add(new RichPoint(new LatLng(location.getLatitude(),location.getLongitude())).color(color));
        if (refresh) richLayer.refresh();
    }

    @Override
    public void setPoints(List<Location> points) {
        if (points==null) return;
        for(int i = 0; i < points.size(); i++) {
            addPoint(points.get(i),false);
        }
        richLayer.refresh();
    }

    @Override
    public void refresh() {
        richLayer.refresh();
    }

    public void clear() {
        richLayer.removeShape(polyline);
    }

    @Override
    public void hide() {
        richLayer.removeShape(polyline);
    }

    @Override
    public void show() {
        richLayer.addShape(polyline);
    }

    public float getSpeedProportion(double metersPerSecond) {
        return (float)(Math.max(Math.min(metersPerSecond, HIGH_SPEED_CLAMP_MpS), LOW_SPEED_CLAMP_MpS) / HIGH_SPEED_CLAMP_MpS);
    }

    private int interpolateColor(float proportion) {
        int rTotal = 0, gTotal = 0, bTotal = 0;
        // We correct the ratio to COLORS.length - 1 so that
        // for i == COLORS.length - 1 and p == 1, then the final ratio is 1 (see below)
        float p = proportion * (COLORS.length - 1);

        for (int i = 0; i < COLORS.length; i++) {
            // The ratio mostly resides on the 1 - Math.abs(p - i) calculation :
            // Since for p == i, then the ratio is 1 and for p == i + 1 or p == i -1, then the ratio is 0
            // This calculation works BECAUSE p lies within [0, length - 1] and i lies within [0, length - 1] as well
            float iRatio = Math.max(1 - Math.abs(p - i), 0.0f);
            rTotal += (int)(Color.red(COLORS[i]) * iRatio);
            gTotal += (int)(Color.green(COLORS[i]) * iRatio);
            bTotal += (int)(Color.blue(COLORS[i]) * iRatio);
        }

        return Color.rgb(rTotal, gTotal, bTotal);
    }
}

Google's polyline: google_polyline Richpolyline rich_polyline

Also here's the method where I load the points and set the start/end markers

    private void loadTrackFromDB(long trackId) {

        Cursor data = null;
        ArrayList<Location> points = new ArrayList<>();

        try {
            data = SQLite.select().from(TrackLocation.class).where(TrackLocation_Table.track_id.eq(trackId)).orderBy(TrackLocation_Table.time, true).query();
        } catch (Exception e) {
            return;
        }

        // repopulate the map
        if (data == null) {
            return;
        }

        try {
            if (!data.moveToFirst()) {
                return;
            }
            int latIndex = data.getColumnIndex("latitude");
            int lonIndex = data.getColumnIndex("longitude");
            int speedIndex = data.getColumnIndex("speed");

            LatLng point = new LatLng(data.getDouble(latIndex), data.getDouble(lonIndex));
            Location location = new Location("snocru");
            location.setLatitude(point.latitude);
            location.setLongitude(point.longitude);
            location.setSpeed(data.getFloat(speedIndex));

            BitmapDescriptor startPinDescriptor = BitmapDescriptorFactory.fromResource(R.drawable.pin_green);
            MarkerOptions startMarkerOptions = new MarkerOptions()
                    .title(getString(R.string.track_start))
                    .position(point)
                    .icon(startPinDescriptor);
            startMarker = googleMap.addMarker(startMarkerOptions);

            points.add(location);
            while (data.moveToNext()) {
                location = new Location("snocru");
                location.setLatitude(data.getDouble(latIndex));
                location.setLongitude(data.getDouble(lonIndex));
                location.setSpeed(data.getFloat(speedIndex));
                points.add(location);
            }
            mapPolyline.setPoints(points);
            speedGradientPolyline.setPoints(points);
            densityMap.setPoints(points);

            point = new LatLng(location.getLatitude(),location.getLongitude());

            // set the end marker
            if (curLocMarker != null) {
                curLocMarker.remove();
            }
            MarkerOptions currentLocationMarker = new MarkerOptions()
                    .title(getString(R.string.current_location))
                    .position(point)
                    .icon(BitmapDescriptorFactory.fromResource(R.drawable.pin_blue));
            curLocMarker = googleMap.addMarker(currentLocationMarker);

        } catch (Throwable t) {
            Log.e(TAG, "Error reloading the map", t);
            try {
                Crashlytics.logException(t);
            } catch (Throwable ct) {
            }
        } finally {
            data.close();
        }
    }
brwskitime commented 7 years ago

Here's the class for the google polyline.

public class MapPolyline implements IPolyline {

    private GoogleMap googleMap;

    private Polyline polyline;

    public MapPolyline(GoogleMap googleMap, int color, float lineWidth) {
        this.googleMap = googleMap;
        this.polyline = googleMap.addPolyline(new PolylineOptions().geodesic(false).color(color).width(lineWidth));
    }

    @Override
    public void addPoint(Location location) {
        List<LatLng> points = polyline.getPoints();
        points.add(new LatLng(location.getLatitude(),location.getLongitude()));
        polyline.setPoints(points);
    }

    @Override
    public void setPoints(List<Location> locations) {
        if (locations == null) locations = new ArrayList<>();
        List<LatLng> points = polyline.getPoints();
        points.clear();
        for(int i = 0; i < locations.size(); i++) {
            Location location = locations.get(i);
            points.add(new LatLng(location.getLatitude(),location.getLongitude()));
        }
        polyline.setPoints(points);
    }

    @Override
    public void hide() {
        polyline.setVisible(false);
    }

    @Override
    public void show() {
        polyline.setVisible(true);
    }

    @Override
    public void clear() {
        polyline.setPoints(new ArrayList<LatLng>());
    }

    @Override
    public void refresh() {

    }
}
antoniocarlon commented 7 years ago

I have tested the project and I still can't find the problem.

Could you test it using the following MapActivity class? It will show you a green RichPolyline (richmaps), a blue Polyline (Google Maps) and two Markers:

public class MapsActivity extends FragmentActivity implements OnMapReadyCallback, GoogleMap.OnCameraIdleListener {
    private RichLayer richLayer;
    private View mMapView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_maps);

        SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
                .findFragmentById(R.id.map);
        mapFragment.getMapAsync(this);
        mMapView = mapFragment.getView();
    }

    @Override
    public void onMapReady(final GoogleMap googleMap) {
        googleMap.setOnCameraIdleListener(this);

        richLayer = new RichLayer.Builder(mMapView, googleMap).zIndex(0).build();

        LatLng ll1 = new LatLng(43.591921, -116.211426);
        LatLng ll2 = new LatLng(43.591834, -116.211456);
        LatLng ll3 = new LatLng(43.591741, -116.211429);

        googleMap.moveCamera(CameraUpdateFactory.newLatLngZoom(ll2, 21));
        googleMap.addMarker(new MarkerOptions().position(ll1).icon(
                BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)));
        googleMap.addMarker(new MarkerOptions().position(ll3).icon(
                BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)));

        RichPolylineOptions polylineOpts = new RichPolylineOptions(null)
                .zIndex(3)
                .strokeWidth(15)
                .strokeColor(Color.GREEN)
                .linearGradient(true)
                .add(new RichPoint(ll1))
                .add(new RichPoint(ll2))
                .add(new RichPoint(ll3));
        richLayer.addShape(polylineOpts.build());

        googleMap.addPolyline(new PolylineOptions()
                .add(ll1)
                .add(ll2)
                .add(ll3)
                .color(Color.BLUE)
                .width(3));
    }

    @Override
    public void onCameraIdle() {
        richLayer.refresh();
    }
}
brwskitime commented 7 years ago

Ok finally I figured out what's happening. You can set padding on the google map and google's polyline takes it into account.

We added bottom padding to account for the panel height we draw over the top of our map.

googleMap.setPadding(0, 0, 0, slidingUpPanelLayout.getPanelHeight());

This explains why the line was always shifted up in the y direction.

antoniocarlon commented 7 years ago

I have just updated richmaps to take into account vertical (top/bottom) padding. I have tried to do the same for horizontal (left/right) padding but it seems to work different so I need to put more effort on it. I hope this gets you closer to solve your issue.

brwskitime commented 7 years ago

Thanks! By the way thanks for creating this project. Other than the padding thing, everything worked better than expected right out of the box.

antoniocarlon commented 7 years ago

Thank you for your patience. Glad to hear it was helpful! :)