mapbox / mapbox-gl-draw

Draw tools for mapbox-gl-js
https://www.mapbox.com/mapbox-gl-js/example/mapbox-gl-draw/
ISC License
942 stars 590 forks source link

symbol text disappearing #921

Closed marksmall closed 4 years ago

marksmall commented 4 years ago

mapbox-gl-js version: 1.3.0 mapbox-gl-draw version: 1.1.2

I've experienced this problem for at least a year, with other version combinations.

Steps to Trigger Behavior

  1. Change to Custom Mode: RadiusMode (see radius.js below, I've also included theme.js for completeness)
  2. RadiusMode (based on draw_line also creates a Point feature (for text) and a Polygon feature to represent the circle. While drawing, the measurement text is visible.
  3. Once drawing stops, text disappears, but the line and circle remain.

Expected Behavior

I expect the line,circle and text to all be retained once drawn finishes.

Actual Behavior

Line and circle retained, but text disappears. You can see the relevant code below. I initially thought it might be to do with hot/cold features, but I added another layer and updated the filters to check if the feature was active or not, but it made no difference.

I have managed to get this to work in a very hacky way.

  1. Add my own symbol source/layer to map.
  2. Implement the draw.render function to retrieve all features and for each type handled, Point, Polygon etc create a new feature from all features.
  3. Add new features to measurements source vial the setData function.

This solution just feels wrong, if the text is rendered while drawing, I don't see why it isn't rendered when drawing completes. I'm sure I'm missing something, just can't put my finger exactly on what

Code

Radius

import MapboxDraw from '@mapbox/mapbox-gl-draw';
import Constants from '@mapbox/mapbox-gl-draw/src/constants';
import doubleClickZoom from '@mapbox/mapbox-gl-draw/src/lib/double_click_zoom';
import length from '@turf/length';
import circleFn from '@turf/circle';

import { toDecimalPlaces } from '../../utils/numbers';

const RadiusMode = { ...MapboxDraw.modes.draw_line_string };

function getDisplayMeasurements(feature) {
  const lineLengthInMeters = length(feature, { units: 'kilometers' }) * 1000;

  let metricUnits = 'm';
  let metricMeasurement;
  metricMeasurement = lineLengthInMeters;
  if (lineLengthInMeters >= 1000) {
    // if over 1000 meters, upgrade metric
    metricMeasurement = lineLengthInMeters / 1000;
    metricUnits = 'km';
  }

  let standardUnits = 'feet';
  let imperialMeasurement;
  imperialMeasurement = lineLengthInMeters * 3.28084;
  if (imperialMeasurement >= 5280) {
    // if over 5280 feet, upgrade standard
    imperialMeasurement /= 5280;
    standardUnits = 'mi';
  }

  return {
    metric: `${toDecimalPlaces(metricMeasurement, 2)} ${metricUnits}`,
    standard: `${toDecimalPlaces(imperialMeasurement, 2)} ${standardUnits}`
  };
}

RadiusMode.onSetup = function(opts) {
  const props = MapboxDraw.modes.draw_line_string.onSetup.call(this, opts);
  props.line.properties = {
    ...props.line.properties,
    ...opts
  };

  const circle = this.newFeature({
    type: 'Feature',
    properties: {
      meta: 'feature',
      ...opts
    },
    geometry: {
      type: 'Polygon',
      coordinates: [[0, 0]]
    }
  });
  this.addFeature(circle);

  const label = this.newFeature({
    type: 'Feature',
    properties: {
      meta: 'feature',
      ...opts
    },
    geometry: {
      type: 'Point',
      coordinates: [0, 0]
    }
  });
  this.addFeature(label);

  return {
    ...props,
    circle,
    label
  };
};

RadiusMode.clickAnywhere = function(state, event) {
  const {
    lngLat: { lng, lat }
  } = event;
  // this ends the drawing after the user creates a second point, triggering this.onStop
  if (state.currentVertexPosition === 1) {
    state.line.addCoordinate(0, lng, lat);
    return this.changeMode('simple_select', { featureIds: [state.line.id] });
  }
  this.updateUIClasses({ mouse: 'add' });
  state.line.updateCoordinate(state.currentVertexPosition, lng, lat);
  if (state.direction === 'forward') {
    state.currentVertexPosition += 1; // eslint-disable-line
    state.line.updateCoordinate(state.currentVertexPosition, lng, lat);
  } else {
    state.line.addCoordinate(0, lng, lat);
  }

  return null;
};

RadiusMode.onMouseMove = function(state, event) {
  MapboxDraw.modes.draw_line_string.onMouseMove.call(this, state, event);
  const geojson = state.line.toGeoJSON();
  const center = geojson.geometry.coordinates[0];
  const radius = length(geojson, { units: 'kilometers' });

  const options = {
    steps: 60,
    units: 'kilometers',
    properties: { parent: state.line.properties.id, ...state.circle.properties }
  };

  if (radius) {
    const circleFeature = circleFn(center, radius, options);
    state.circle.setCoordinates(circleFeature.geometry.coordinates);
  }

  const displayMeasurements = getDisplayMeasurements(geojson);
  state.label.setCoordinates(geojson.geometry.coordinates[0]);
  state.label.properties = {
    ...state.label.properties,
    radiusMetric: displayMeasurements.metric,
    radiusStandard: displayMeasurements.standard,
    parent: state.line.id
  };
};

RadiusMode.onStop = function(state) {
  doubleClickZoom.enable(this);
  this.activateUIButton();

  // check to see if we've deleted this feature
  if (this.getFeature(state.line.id) === undefined) return;

  //remove last added coordinate
  state.line.removeCoordinate('0');
  if (state.line.isValid()) {
    this.map.fire(Constants.events.CREATE, {
      features: [state.line.toGeoJSON(), state.circle.toGeoJSON(), state.label.toGeoJSON()]
    });
  } else {
    this.deleteFeature([state.line.id], { silent: true });
    this.deleteFeature([state.circle.id], { silent: true });
    this.deleteFeature([state.label.id], { silent: true });
    this.changeMode(Constants.modes.SIMPLE_SELECT, {}, { silent: true });
  }
};

RadiusMode.toDisplayFeatures = function(state, geojson, display) {
  const isActiveLine = geojson.properties.id === state.line.id;
  geojson.properties.active = isActiveLine ? Constants.activeStates.ACTIVE : Constants.activeStates.INACTIVE;
  if (!isActiveLine) return display(geojson);

  // Only render the line if it has at least one real coordinate
  if (geojson.geometry.coordinates.length < 2) return null;

  // Display the line as it is drawn.
  display(geojson);

  display(state.label.toGeoJSON());
};

export default RadiusMode;

theme.js

export default [
  {
    id: 'gl-draw-polygon-fill-inactive',
    type: 'fill',
    filter: ['all', ['==', 'active', 'false'], ['==', '$type', 'Polygon'], ['!=', 'mode', 'static']],
    paint: {
      'fill-color': ['get', 'user_fillColour'],
      'fill-outline-color': '#3bb2d0',
      'fill-opacity': ['get', 'user_fillOpacity']
    }
  },
  {
    id: 'gl-draw-polygon-fill-active',
    type: 'fill',
    filter: ['all', ['==', 'active', 'true'], ['==', '$type', 'Polygon']],
    paint: {
      'fill-color': '#fbb03b',
      'fill-outline-color': '#fbb03b',
      'fill-opacity': ['get', 'user_fillOpacity']
    }
  },
  {
    id: 'gl-draw-polygon-midpoint',
    type: 'circle',
    filter: ['all', ['==', '$type', 'Point'], ['==', 'meta', 'midpoint']],
    paint: {
      'circle-radius': 3,
      'circle-color': 'purple'
    }
  },
  {
    id: 'gl-draw-solid-polygon-stroke-inactive',
    type: 'line',
    filter: [
      'all',
      ['==', 'active', 'false'],
      ['==', '$type', 'Polygon'],
      ['!=', 'mode', 'static'],
      ['==', 'user_lineTypeName', 'solid']
    ],
    layout: {
      'line-cap': 'round',
      'line-join': 'round'
    },
    paint: {
      'line-color': ['get', 'user_lineColour'],
      'line-width': ['get', 'user_lineWidth']
    }
  },
  {
    id: 'gl-draw-dashed-polygon-stroke-inactive',
    type: 'line',
    filter: [
      'all',
      ['==', 'active', 'false'],
      ['==', '$type', 'Polygon'],
      ['!=', 'mode', 'static'],
      ['==', 'user_lineTypeName', 'dashed']
    ],
    layout: {
      'line-cap': 'round',
      'line-join': 'round'
    },
    paint: {
      'line-color': ['get', 'user_lineColour'],
      'line-dasharray': [2, 2],
      'line-width': ['get', 'user_lineWidth']
    }
  },
  {
    id: 'gl-draw-dotted-polygon-stroke-inactive',
    type: 'line',
    filter: [
      'all',
      ['==', 'active', 'false'],
      ['==', '$type', 'Polygon'],
      ['!=', 'mode', 'static'],
      ['==', 'user_lineTypeName', 'dotted']
    ],
    layout: {
      'line-cap': 'round',
      'line-join': 'round'
    },
    paint: {
      'line-color': ['get', 'user_lineColour'],
      'line-dasharray': [0.2, 2],
      'line-width': ['get', 'user_lineWidth']
    }
  },
  {
    id: 'gl-draw-polygon-stroke-active',
    type: 'line',
    filter: ['all', ['==', 'active', 'true'], ['==', '$type', 'Polygon']],
    layout: {
      'line-cap': 'round',
      'line-join': 'round'
    },
    paint: {
      'line-color': '#fbb03b',
      'line-dasharray': [0.2, 2],
      'line-width': ['get', 'user_lineWidth']
    }
  },
  {
    id: 'gl-draw-solid-line-inactive',
    type: 'line',
    filter: [
      'all',
      ['==', 'active', 'false'],
      ['==', '$type', 'LineString'],
      ['!=', 'mode', 'static'],
      ['==', 'user_lineTypeName', 'solid']
    ],
    layout: {
      'line-cap': 'round',
      'line-join': 'round'
    },
    paint: {
      'line-color': ['get', 'user_lineColour'],
      'line-width': ['get', 'user_lineWidth']
    }
  },
  {
    id: 'gl-draw-dashed-line-inactive',
    type: 'line',
    filter: [
      'all',
      ['==', 'active', 'false'],
      ['==', '$type', 'LineString'],
      ['!=', 'mode', 'static'],
      ['==', 'user_lineTypeName', 'dashed']
    ],
    layout: {
      'line-cap': 'round',
      'line-join': 'round'
    },
    paint: {
      'line-color': ['get', 'user_lineColour'],
      'line-dasharray': [2, 2],
      'line-width': ['get', 'user_lineWidth']
    }
  },
  {
    id: 'gl-draw-dotted-line-inactive',
    type: 'line',
    filter: [
      'all',
      ['==', 'active', 'false'],
      ['==', '$type', 'LineString'],
      ['!=', 'mode', 'static'],
      ['==', 'user_lineTypeName', 'dotted']
    ],
    layout: {
      'line-cap': 'round',
      'line-join': 'round'
    },
    paint: {
      'line-color': ['get', 'user_lineColour'],
      'line-dasharray': [0.2, 2],
      'line-width': ['get', 'user_lineWidth']
    }
  },
  {
    id: 'gl-draw-line-active',
    type: 'line',
    filter: ['all', ['==', '$type', 'LineString'], ['==', 'active', 'true']],
    layout: {
      'line-cap': 'round',
      'line-join': 'round'
    },
    paint: {
      'line-color': '#fbb03b',
      'line-dasharray': [0.2, 2],
      'line-width': ['get', 'user_lineWidth']
    }
  },
  {
    id: 'gl-draw-line-label',
    type: 'symbol',
    filter: ['all', ['==', '$type', 'Point'], ['has', 'radiusMetric']],
    layout: {
      'text-field': '{radiusMetric} \n {radiusStandard}',
      'text-anchor': 'left',
      'text-offset': [0, 0],
      'text-size': 16
    },
    paint: {
      'text-color': 'rgba(0, 0, 0, 1)',
      'text-halo-color': 'rgba(255, 255, 255, 1)',
      'text-halo-width': 3,
      'text-halo-blur': 1
    }
  },
  {
    id: 'gl-draw-polygon-and-line-vertex-stroke-inactive',
    type: 'circle',
    filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point'], ['!=', 'mode', 'static']],
    paint: {
      'circle-radius': 5,
      'circle-color': '#fff'
    }
  },
  {
    id: 'gl-draw-polygon-and-line-vertex-inactive',
    type: 'circle',
    filter: ['all', ['==', 'meta', 'vertex'], ['==', '$type', 'Point'], ['!=', 'mode', 'static']],
    paint: {
      'circle-radius': 3,
      'circle-color': '#fbb03b'
    }
  },
  {
    id: 'gl-draw-point-stroke-inactive',
    type: 'circle',
    filter: [
      'all',
      ['==', 'active', 'false'],
      ['==', '$type', 'Point'],
      ['==', 'meta', 'feature'],
      ['!=', 'mode', 'static']
    ],
    paint: {
      'circle-radius': 7,
      'circle-opacity': 1,
      'circle-color': '#fff'
    }
  },
  {
    id: 'gl-draw-point-stroke-active',
    type: 'circle',
    filter: ['all', ['==', '$type', 'Point'], ['==', 'active', 'true'], ['!=', 'meta', 'midpoint']],
    paint: {
      'circle-radius': 7,
      'circle-color': '#fff'
    }
  },
  {
    id: 'gl-draw-point-inactive',
    type: 'circle',
    filter: [
      'all',
      ['==', 'active', 'false'],
      ['==', '$type', 'Point'],
      ['==', 'meta', 'feature'],
      ['!=', 'mode', 'static']
    ],
    paint: {
      'circle-radius': 5,
      'circle-color': 'green'
    }
  },
  {
    id: 'gl-draw-point-active',
    type: 'circle',
    filter: ['all', ['==', '$type', 'Point'], ['!=', 'meta', 'midpoint'], ['==', 'active', 'true']],
    paint: {
      'circle-radius': 5,
      'circle-color': 'blue'
    }
  },
  {
    id: 'gl-draw-polygon-fill-static',
    type: 'fill',
    filter: ['all', ['==', 'mode', 'static'], ['==', '$type', 'Polygon']],
    paint: {
      'fill-color': '#404040',
      'fill-outline-color': '#404040',
      'fill-opacity': 0.1
    }
  },
  {
    id: 'gl-draw-polygon-stroke-static',
    type: 'line',
    filter: ['all', ['==', 'mode', 'static'], ['==', '$type', 'Polygon']],
    layout: {
      'line-cap': 'round',
      'line-join': 'round'
    },
    paint: {
      'line-color': '#404040',
      'line-width': 2
    }
  },
  {
    id: 'gl-draw-line-static',
    type: 'line',
    filter: ['all', ['==', 'mode', 'static'], ['==', '$type', 'LineString']],
    layout: {
      'line-cap': 'round',
      'line-join': 'round'
    },
    paint: {
      'line-color': '#404040',
      'line-width': 2
    }
  },
  {
    id: 'gl-draw-point-static',
    type: 'circle',
    filter: ['all', ['==', 'mode', 'static'], ['==', '$type', 'Point']],
    paint: {
      'circle-radius': 5,
      'circle-color': '#404040'
    }
  },
  {
    id: 'gl-draw-point-label',
    type: 'symbol',
    filter: ['all', ['==', '$type', 'Point']],
    layout: {
      'text-field': '{label}',
      'text-anchor': 'left',
      'text-offset': [0, 0],
      'text-size': 16,
      'text-allow-overlap': true
    },
    paint: {
      'text-color': 'rgba(0, 0, 0, 1)',
      'text-halo-color': 'rgba(255, 255, 255, 1)',
      'text-halo-width': 3,
      'text-halo-blur': 1
    }
  }
];
marksmall commented 4 years ago

Figured it out, it's to do with properties, when drawing, the property radiusMetric for instance exists, but when inactive, the feature has user_radiusMetric. This seems like a design flaw on my part, therefore closing this issue.