Omaha #4388 Use the record fill value in satellite resource.
Former-commit-id: d13b4cfda688ecec9dfa0563787ff6370c980443
This commit is contained in:
parent
5d208bb120
commit
60c93552a3
7 changed files with 135 additions and 50 deletions
|
@ -19,11 +19,13 @@
|
|||
**/
|
||||
package com.raytheon.viz.satellite.inventory;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.measure.unit.Unit;
|
||||
import javax.measure.unit.UnitFormat;
|
||||
|
||||
import org.geotools.coverage.grid.GridEnvelope2D;
|
||||
|
@ -33,11 +35,15 @@ import org.opengis.referencing.operation.TransformException;
|
|||
|
||||
import com.raytheon.uf.common.dataplugin.satellite.SatMapCoverage;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.SatelliteRecord;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.units.generic.GenericPixel;
|
||||
import com.raytheon.uf.common.datastorage.DataStoreFactory;
|
||||
import com.raytheon.uf.common.datastorage.Request;
|
||||
import com.raytheon.uf.common.datastorage.records.ByteDataRecord;
|
||||
import com.raytheon.uf.common.datastorage.records.IDataRecord;
|
||||
import com.raytheon.uf.common.geospatial.interpolation.GridDownscaler;
|
||||
import com.raytheon.uf.common.inventory.data.AbstractRequestableData;
|
||||
import com.raytheon.uf.common.inventory.exception.DataCubeException;
|
||||
import com.raytheon.viz.satellite.tileset.SatDataRetriever;
|
||||
|
||||
/**
|
||||
* Satellite record which performs derived parameter calculations to get data
|
||||
|
@ -50,6 +56,7 @@ import com.raytheon.uf.common.inventory.exception.DataCubeException;
|
|||
* Date Ticket# Engineer Description
|
||||
* ------------- -------- ----------- --------------------------
|
||||
* Apr 09, 2014 2947 bsteffen Initial creation
|
||||
* Apr 15, 2014 4388 bsteffen Set Fill Value.
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -134,15 +141,50 @@ public class DerivedSatelliteRecord extends SatelliteRecord {
|
|||
throw new DataCubeException(e);
|
||||
}
|
||||
gridGeometry = new GridGeometry2D(range, env);
|
||||
} else if (req.getType() == Request.Type.POINT
|
||||
&& req.getPoints().length == 1) {
|
||||
Point p = req.getPoints()[0];
|
||||
GridEnvelope2D range = new GridEnvelope2D(p.x, p.y, 1, 1);
|
||||
Envelope env;
|
||||
try {
|
||||
env = gridGeometry.gridToWorld(range);
|
||||
} catch (TransformException e) {
|
||||
throw new DataCubeException(e);
|
||||
}
|
||||
gridGeometry = new GridGeometry2D(range, env);
|
||||
} else if (req.getType() != Request.Type.ALL) {
|
||||
throw new DataCubeException(
|
||||
"Unsupported request type for derived satellite data: "
|
||||
+ req.getType());
|
||||
}
|
||||
Object dataValue = requestableData.getDataValue(gridGeometry);
|
||||
setDerivedFillValue(dataValue);
|
||||
setMessageData(dataValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a fill value on the dataValue if applicable. Some derived parameters
|
||||
* operate on unsigned byte data and return signed byte data and need a fill
|
||||
* value set to composite properly.
|
||||
*/
|
||||
private void setDerivedFillValue(Object dataValue) {
|
||||
IDataRecord dataRecord = null;
|
||||
if (dataValue instanceof IDataRecord[]) {
|
||||
IDataRecord[] dataRecords = (IDataRecord[]) dataValue;
|
||||
if (dataRecords.length == 1) {
|
||||
dataRecord = dataRecords[0];
|
||||
}
|
||||
} else if (dataValue instanceof IDataRecord) {
|
||||
dataRecord = (IDataRecord) dataValue;
|
||||
}
|
||||
if (dataRecord instanceof ByteDataRecord) {
|
||||
Unit<?> unit = SatDataRetriever.getRecordUnit(this);
|
||||
if (unit instanceof GenericPixel) {
|
||||
dataRecord.setFillValue(Byte.MIN_VALUE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Set<SatelliteRecord> findBaseRecords(
|
||||
AbstractRequestableData data) {
|
||||
if (data instanceof SatelliteRequestableData) {
|
||||
|
|
|
@ -21,7 +21,6 @@ package com.raytheon.viz.satellite.inventory;
|
|||
|
||||
import java.awt.Point;
|
||||
import java.awt.Rectangle;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.measure.unit.Unit;
|
||||
|
||||
|
@ -61,6 +60,7 @@ import com.raytheon.viz.satellite.tileset.SatDataRetriever;
|
|||
* Date Ticket# Engineer Description
|
||||
* ------------- -------- ----------- --------------------------
|
||||
* Apr 09, 2014 2947 bsteffen Initial creation
|
||||
* Apr 15, 2014 4388 bsteffen Set Fill Value.
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -153,20 +153,13 @@ public class SatelliteRequestableData extends AbstractRequestableData {
|
|||
} catch (FactoryException e) {
|
||||
throw new DataCubeException(e);
|
||||
}
|
||||
Map<String, Object> attrs = dataRecord.getDataAttributes();
|
||||
Number fillValue = dataRecord.getFillValue();
|
||||
dataRecord = DataStoreFactory.createStorageRecord(
|
||||
dataRecord.getName(), dataRecord.getGroup(),
|
||||
dest.getArray(), 2, new long[] { requestRange.width,
|
||||
requestRange.height });
|
||||
dataRecord.setFillValue(0);
|
||||
if (attrs != null) {
|
||||
Number fill = (Number) attrs
|
||||
.get(SatelliteRecord.SAT_FILL_VALUE);
|
||||
|
||||
if (fill != null) {
|
||||
dataRecord.setFillValue(fill);
|
||||
}
|
||||
}
|
||||
dataRecord.setFillValue(fillValue);
|
||||
return dataRecord;
|
||||
} else {
|
||||
return CubeUtil.retrieveData(record, record.getPluginName(),
|
||||
|
|
|
@ -38,6 +38,8 @@ import org.opengis.referencing.FactoryException;
|
|||
import org.opengis.referencing.crs.CoordinateReferenceSystem;
|
||||
import org.opengis.referencing.operation.TransformException;
|
||||
|
||||
import com.raytheon.uf.common.colormap.ColorMapException;
|
||||
import com.raytheon.uf.common.colormap.ColorMapLoader;
|
||||
import com.raytheon.uf.common.colormap.IColorMap;
|
||||
import com.raytheon.uf.common.colormap.prefs.ColorMapParameters;
|
||||
import com.raytheon.uf.common.colormap.prefs.ColorMapParameters.PersistedParameters;
|
||||
|
@ -45,10 +47,15 @@ import com.raytheon.uf.common.colormap.prefs.DataMappingPreferences;
|
|||
import com.raytheon.uf.common.dataplugin.PluginDataObject;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.SatMapCoverage;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.SatelliteRecord;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.units.generic.GenericPixel;
|
||||
import com.raytheon.uf.common.datastorage.DataStoreFactory;
|
||||
import com.raytheon.uf.common.datastorage.Request;
|
||||
import com.raytheon.uf.common.datastorage.records.IDataRecord;
|
||||
import com.raytheon.uf.common.datastorage.records.ShortDataRecord;
|
||||
import com.raytheon.uf.common.geospatial.IGridGeometryProvider;
|
||||
import com.raytheon.uf.common.geospatial.ReferencedCoordinate;
|
||||
import com.raytheon.uf.common.geospatial.data.GeographicDataSource;
|
||||
import com.raytheon.uf.common.inventory.exception.DataCubeException;
|
||||
import com.raytheon.uf.common.status.UFStatus.Priority;
|
||||
import com.raytheon.uf.common.style.ParamLevelMatchCriteria;
|
||||
import com.raytheon.uf.common.style.StyleException;
|
||||
import com.raytheon.uf.common.style.StyleManager;
|
||||
|
@ -62,7 +69,6 @@ import com.raytheon.uf.common.style.level.SingleLevel;
|
|||
import com.raytheon.uf.common.time.DataTime;
|
||||
import com.raytheon.uf.viz.core.DrawableImage;
|
||||
import com.raytheon.uf.viz.core.IGraphicsTarget;
|
||||
import com.raytheon.uf.viz.core.drawables.ColorMapLoader;
|
||||
import com.raytheon.uf.viz.core.drawables.IRenderable;
|
||||
import com.raytheon.uf.viz.core.drawables.PaintProperties;
|
||||
import com.raytheon.uf.viz.core.drawables.ext.IImagingExtension.ImageProvider;
|
||||
|
@ -79,6 +85,7 @@ import com.raytheon.uf.viz.core.rsc.interrogation.InterrogateMap;
|
|||
import com.raytheon.uf.viz.core.rsc.interrogation.InterrogationKey;
|
||||
import com.raytheon.uf.viz.core.rsc.interrogation.Interrogator;
|
||||
import com.raytheon.uf.viz.core.rsc.interrogation.StringInterrogationKey;
|
||||
import com.raytheon.uf.viz.datacube.DataCubeContainer;
|
||||
import com.raytheon.viz.satellite.SatelliteConstants;
|
||||
import com.raytheon.viz.satellite.inventory.DerivedSatelliteRecord;
|
||||
import com.raytheon.viz.satellite.tileset.SatDataRetriever;
|
||||
|
@ -127,7 +134,7 @@ import com.raytheon.viz.awipstools.IToolChangedListener;
|
|||
* Oct 15, 2014 3681 bsteffen create renderable in interrogate if necessary.
|
||||
* Feb 17, 2015 4135 bsteffen Set no data value for derived products.
|
||||
* Mar 3, 2015 DCS 14960 jgerth Retrieve legend from style rules if available
|
||||
*
|
||||
* Apr 15, 2014 4388 bsteffen Use fill value from record.
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -333,6 +340,41 @@ public class SatResource extends
|
|||
cmName = colorMapParameters.getColorMapName();
|
||||
}
|
||||
}
|
||||
Unit<?> unit = SatDataRetriever.getRecordUnit(record);
|
||||
|
||||
|
||||
/*
|
||||
* TODO default to NaN instead of 0. 0 is the default to support older
|
||||
* data(before the gini decoder set a fill value), when all decoders and
|
||||
* all decoded data has a proper fill value this should be changed to
|
||||
* Double.NaN.
|
||||
*/
|
||||
double fillValue = 0;
|
||||
double defaultMin = 0;
|
||||
double defaultMax = 0xff;
|
||||
try {
|
||||
String dataSetName = DataStoreFactory.createDataSetName(null,
|
||||
SatelliteRecord.SAT_DATASET_NAME, 0);
|
||||
IDataRecord dataRecord = DataCubeContainer.getDataRecord(record,
|
||||
Request.buildPointRequest(new java.awt.Point(0, 0)),
|
||||
dataSetName)[0];
|
||||
if (dataRecord instanceof ShortDataRecord) {
|
||||
defaultMax = 0xffff;
|
||||
}
|
||||
Number fillObj = dataRecord.getFillValue();
|
||||
if (fillObj != null) {
|
||||
fillValue = fillObj.doubleValue();
|
||||
}
|
||||
Unit<?> dataUnit = SatDataRetriever.getDataUnit(unit, dataRecord);
|
||||
if (dataUnit != null && unit != null && dataUnit.isCompatible(unit)) {
|
||||
UnitConverter converter = dataUnit.getConverterTo(unit);
|
||||
defaultMin = converter.convert(defaultMin);
|
||||
defaultMax = converter.convert(defaultMax);
|
||||
}
|
||||
} catch (DataCubeException e) {
|
||||
statusHandler.handle(Priority.WARN,
|
||||
"Unable to request sample record", e);
|
||||
}
|
||||
|
||||
SingleLevel level = new SingleLevel(Level.LevelType.SURFACE);
|
||||
String physicalElement = record.getPhysicalElement();
|
||||
|
@ -342,7 +384,6 @@ public class SatResource extends
|
|||
match.setParameterName(Arrays.asList(physicalElement));
|
||||
match.setLevels(Arrays.asList((Level) level));
|
||||
match.setCreatingEntityNames(Arrays.asList(record.getCreatingEntity()));
|
||||
Unit<?> unit = SatDataRetriever.getRecordUnit(record);
|
||||
String lg = null;
|
||||
try {
|
||||
StyleRule sr = StyleManager.getInstance().getStyleRule(
|
||||
|
@ -360,8 +401,8 @@ public class SatResource extends
|
|||
}
|
||||
DataScale range = new DataScale();
|
||||
range.setScaleType(DataScale.Type.LINEAR);
|
||||
range.setMinValue(0.0);
|
||||
range.setMaxValue(255.0);
|
||||
range.setMinValue(defaultMin);
|
||||
range.setMaxValue(defaultMax);
|
||||
preferences.setDataScale(range);
|
||||
} else {
|
||||
preferences = (ImagePreferences) sr.getPreferences();
|
||||
|
@ -392,7 +433,11 @@ public class SatResource extends
|
|||
if (cmName == null) {
|
||||
cmName = "Sat/VIS/ZA (Vis Default)";
|
||||
}
|
||||
colorMap = ColorMapLoader.loadColorMap(cmName);
|
||||
try {
|
||||
colorMap = ColorMapLoader.loadColorMap(cmName);
|
||||
} catch (ColorMapException e) {
|
||||
throw new VizException("Unable to load clormap: " + cmName, e);
|
||||
}
|
||||
}
|
||||
|
||||
if (colorMap != null) {
|
||||
|
@ -402,18 +447,9 @@ public class SatResource extends
|
|||
if (persisted != null) {
|
||||
colorMapParameters.applyPersistedParameters(persisted);
|
||||
}
|
||||
if (colorMapParameters.getDataMapping() == null) {
|
||||
if (unit instanceof GenericPixel) {
|
||||
/**
|
||||
* Generic Pixel only comes from derived parameter which used
|
||||
* signed data so 0 is valid but -128 is used as a no data
|
||||
* value.
|
||||
*/
|
||||
colorMapParameters.setNoDataValue(Byte.MIN_VALUE);
|
||||
} else {
|
||||
colorMapParameters.setNoDataValue(0);
|
||||
}
|
||||
|
||||
if (colorMapParameters.getDataMapping() == null) {
|
||||
colorMapParameters.setNoDataValue(fillValue);
|
||||
}
|
||||
|
||||
getCapability(ColorMapCapability.class).setColorMapParameters(
|
||||
|
|
|
@ -29,7 +29,6 @@ import com.raytheon.uf.common.dataplugin.PluginDataObject;
|
|||
import com.raytheon.uf.common.dataplugin.PluginException;
|
||||
import com.raytheon.uf.common.dataplugin.persist.IPersistable;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.SatMapCoverage;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.SatelliteMessageData;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.SatelliteRecord;
|
||||
import com.raytheon.uf.common.datastorage.DataStoreFactory;
|
||||
import com.raytheon.uf.common.datastorage.IDataStore;
|
||||
|
@ -67,6 +66,8 @@ import com.raytheon.uf.edex.database.query.DatabaseQuery;
|
|||
* Mar 07, 2014 2791 bsteffen Move Data Source/Destination to numeric
|
||||
* plugin.
|
||||
* Nov 04, 2014 2714 bclement removed GINI specific DAOs
|
||||
* Apr 15, 2014 4388 bsteffen Preserve fill value across interpolation levels.
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
* @author bphillip
|
||||
|
@ -113,8 +114,12 @@ public class SatelliteDao extends PluginDao {
|
|||
storageRecord.setCorrelationObject(satRecord);
|
||||
final Map<String, Object> attributes = storageRecord
|
||||
.getDataAttributes();
|
||||
final Float fillValue = getAttribute(attributes,
|
||||
SatelliteRecord.SAT_FILL_VALUE, 0.0f);
|
||||
if (storageRecord.getFillValue() == null) {
|
||||
Number fillValue = getAttribute(attributes,
|
||||
SatelliteRecord.SAT_FILL_VALUE, 0.0f);
|
||||
storageRecord.setFillValue(fillValue);
|
||||
}
|
||||
final Number fillValue = storageRecord.getFillValue();
|
||||
|
||||
// Store the base record.
|
||||
dataStore.addDataRecord(storageRecord);
|
||||
|
@ -142,13 +147,14 @@ public class SatelliteDao extends PluginDao {
|
|||
// data.
|
||||
dr.setDataAttributes(attributes);
|
||||
dr.setProperties(props);
|
||||
dr.setFillValue(fillValue);
|
||||
return dr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public double getFillValue() {
|
||||
// always the same fill value
|
||||
return fillValue;
|
||||
return fillValue.doubleValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -381,10 +387,10 @@ public class SatelliteDao extends PluginDao {
|
|||
*/
|
||||
private IDataRecord createDataRecord(SatelliteRecord satRec, Object data,
|
||||
int downscaleLevel, Rectangle size) {
|
||||
SatelliteMessageData msgData = null;
|
||||
msgData = new SatelliteMessageData(data, size.width, size.height);
|
||||
IDataRecord rec = msgData.getStorageRecord(satRec,
|
||||
String.valueOf(downscaleLevel));
|
||||
long[] sizes = new long[] { size.width, size.height };
|
||||
IDataRecord rec = DataStoreFactory.createStorageRecord(
|
||||
String.valueOf(downscaleLevel),
|
||||
satRec.getDataURI(), data, 2, sizes);
|
||||
rec.setCorrelationObject(satRec);
|
||||
rec.setGroup(DataStoreFactory.createGroupName(satRec.getDataURI(),
|
||||
SatelliteRecord.SAT_DATASET_NAME, true));
|
||||
|
@ -403,11 +409,11 @@ public class SatelliteDao extends PluginDao {
|
|||
* A default value.
|
||||
* @return
|
||||
*/
|
||||
public static Float getAttribute(Map<String, Object> attrs,
|
||||
public static Number getAttribute(Map<String, Object> attrs,
|
||||
String attrName, Float defValue) {
|
||||
Float retValue = defValue;
|
||||
Number retValue = defValue;
|
||||
if ((attrs != null) && (attrName != null)) {
|
||||
retValue = (Float) attrs.get(attrName);
|
||||
retValue = (Number) attrs.get(attrName);
|
||||
}
|
||||
return retValue;
|
||||
}
|
||||
|
|
|
@ -28,11 +28,13 @@ import com.raytheon.uf.common.serialization.annotations.DynamicSerialize;
|
|||
import com.raytheon.uf.common.serialization.annotations.DynamicSerializeElement;
|
||||
|
||||
/**
|
||||
* Encapsulate satellite image data as well as the dimensions of the image grid.
|
||||
* Attributes about the data may also be added. As an example these attributes
|
||||
* could include "scale factor" and/or "fill_value".
|
||||
* @Deprecated this class is not providing anything unique enough to justify
|
||||
* its existence, wherever it is used an IDataRecord of the
|
||||
* appropriate type can be used instead.
|
||||
*
|
||||
* <pre>
|
||||
* TODO this class can be deleted when no decoders are using it.
|
||||
*
|
||||
* <pre>
|
||||
*
|
||||
* SOFTWARE HISTORY
|
||||
*
|
||||
|
@ -41,6 +43,7 @@ import com.raytheon.uf.common.serialization.annotations.DynamicSerializeElement;
|
|||
* Jun 27, 2012 798 jkorman Initial creation
|
||||
* Nov 14, 2013 2393 bclement use datastore factory in
|
||||
* getStorageRecord()
|
||||
* Apr 15, 2014 4388 bsteffen Deprecate
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -48,6 +51,7 @@ import com.raytheon.uf.common.serialization.annotations.DynamicSerializeElement;
|
|||
* @version 1.0
|
||||
*/
|
||||
@DynamicSerialize
|
||||
@Deprecated
|
||||
public class SatelliteMessageData {
|
||||
|
||||
private static final int DATA_DIMS = 2;
|
||||
|
|
|
@ -74,6 +74,7 @@ import com.raytheon.uf.common.serialization.annotations.DynamicSerializeElement;
|
|||
* PluginDataObject.
|
||||
* Aug 30, 2013 2298 rjpeter Make getPluginName abstract
|
||||
* Jun 11, 2014 2061 bsteffen Remove IDecoderGettable
|
||||
* Apr 15, 2014 4388 bsteffen Deprecate SAT_FILL_VALUE
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -105,9 +106,10 @@ public class SatelliteRecord extends PersistablePluginDataObject implements
|
|||
public static final String SAT_DATASET_NAME = DataStoreFactory.DEF_DATASET_NAME;
|
||||
|
||||
/**
|
||||
* The attribute name for a value that will be used to "fill" undefined
|
||||
* data.
|
||||
* @deprecated {@link IDataRecord#setFillValue(Number)} should be used to
|
||||
* store the fill value.
|
||||
*/
|
||||
@Deprecated
|
||||
public static final String SAT_FILL_VALUE = "_FillValue";
|
||||
|
||||
/**
|
||||
|
|
|
@ -37,8 +37,8 @@ import com.raytheon.edex.util.satellite.SatSpatialFactory;
|
|||
import com.raytheon.uf.common.dataplugin.PluginDataObject;
|
||||
import com.raytheon.uf.common.dataplugin.exception.UnrecognizedDataException;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.SatMapCoverage;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.SatelliteMessageData;
|
||||
import com.raytheon.uf.common.dataplugin.satellite.SatelliteRecord;
|
||||
import com.raytheon.uf.common.datastorage.DataStoreFactory;
|
||||
import com.raytheon.uf.common.datastorage.records.IDataRecord;
|
||||
import com.raytheon.uf.common.localization.IPathManager;
|
||||
import com.raytheon.uf.common.localization.LocalizationFile;
|
||||
|
@ -97,6 +97,7 @@ import com.raytheon.uf.edex.plugin.satellite.gini.lookup.NumericLookupTable;
|
|||
* Nov 04, 2014 2714 bclement moved from satellite to satellite.gini
|
||||
* renamed from SatelliteDecoder to GiniDecoder
|
||||
* replaced database lookups with in memory tables
|
||||
* Apr 15, 2014 4388 bsteffen Set Fill Value.
|
||||
*
|
||||
* </pre>
|
||||
*
|
||||
|
@ -447,8 +448,6 @@ public class GiniSatelliteDecoder {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
SatelliteMessageData messageData = new SatelliteMessageData(
|
||||
tempBytes, nx, ny);
|
||||
|
||||
// get the latitude of the first point
|
||||
byteBuffer.position(20);
|
||||
|
@ -537,8 +536,11 @@ public class GiniSatelliteDecoder {
|
|||
record.setTraceId(traceId);
|
||||
record.setCoverage(mapCoverage);
|
||||
// Create the data record.
|
||||
IDataRecord dataRec = messageData.getStorageRecord(record,
|
||||
SatelliteRecord.SAT_DATASET_NAME);
|
||||
long[] sizes = new long[] { nx, ny };
|
||||
IDataRecord dataRec = DataStoreFactory.createStorageRecord(
|
||||
SatelliteRecord.SAT_DATASET_NAME, record.getDataURI(),
|
||||
tempBytes, 2, sizes);
|
||||
dataRec.setFillValue(0);
|
||||
record.setMessageData(dataRec);
|
||||
}
|
||||
timer.stop();
|
||||
|
|
Loading…
Add table
Reference in a new issue