/*******************************************************************************
* Copyright 2016 Geoscience Australia
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package au.gov.ga.earthsci.worldwind.common.layers.borehole.providers;
import gov.nasa.worldwind.geom.Angle;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.geom.Quaternion;
import gov.nasa.worldwind.geom.Vec4;
import gov.nasa.worldwind.render.markers.BasicMarkerAttributes;
import gov.nasa.worldwind.render.markers.BasicMarkerShape;
import gov.nasa.worldwind.render.markers.MarkerAttributes;
import java.awt.Color;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.gdal.osr.CoordinateTransformation;
import au.gov.ga.earthsci.worldwind.common.layers.Bounds;
import au.gov.ga.earthsci.worldwind.common.layers.borehole.Borehole;
import au.gov.ga.earthsci.worldwind.common.layers.borehole.BoreholeImpl;
import au.gov.ga.earthsci.worldwind.common.layers.borehole.BoreholeLayer;
import au.gov.ga.earthsci.worldwind.common.layers.borehole.BoreholeMarkerImpl;
import au.gov.ga.earthsci.worldwind.common.layers.borehole.BoreholeProvider;
import au.gov.ga.earthsci.worldwind.common.layers.borehole.BoreholeSampleImpl;
import au.gov.ga.earthsci.worldwind.common.layers.data.AbstractDataProvider;
import au.gov.ga.earthsci.worldwind.common.util.ColorMap;
/**
* {@link BoreholeProvider} that creates {@link Borehole}s from a GOCAD wells
* file.
*
* @author Michael de Hoog
*/
public class GocadWellBoreholeProvider extends AbstractDataProvider<BoreholeLayer> implements BoreholeProvider
{
public final static Pattern headerPattern = Pattern.compile("GOCAD\\s+Well\\s+.*");
private final static Pattern namePattern = Pattern.compile("name:(.*)\\s*");
private final static Pattern endPattern = Pattern.compile("END\\s*");
private final static Pattern wrefPattern = Pattern
.compile("WREF\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s*");
private final static Pattern vrtxPattern = Pattern
.compile("VRTX\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s*");
private final static Pattern stationPattern = Pattern
.compile("STATION\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s*");
private final static Pattern pathPattern = Pattern
.compile("PATH\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s*");
private final static Pattern tvssPathPattern = Pattern
.compile("TVSS_PATH\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s*");
private final static Pattern tvdPathPattern = Pattern
.compile("TVD_PATH\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s*");
private final static Pattern zonePattern = Pattern
.compile("ZONE\\s+([^\\s]+)\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s+([^\\s]+)\\s*");
private final static Pattern mrkrPattern = Pattern.compile("MRKR\\s+([^\\s]+)\\s+([^\\s]+)\\s+([\\d.\\-]+)\\s*");
private final static Pattern dipPattern = Pattern.compile("DIP\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s*");
private final static Pattern dipDegPattern = Pattern.compile("DIPDEG\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s*");
private final static Pattern normPattern = Pattern
.compile("NORM\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s+([\\d.\\-]+)\\s*");
private Bounds bounds;
private List<BoreholeData> data = new ArrayList<BoreholeData>();
private BoreholeData currentData;
@Override
public Bounds getBounds()
{
return bounds;
}
@Override
public boolean isFollowTerrain()
{
return false;
}
@Override
protected boolean doLoadData(URL url, BoreholeLayer layer)
{
BufferedReader reader = null;
try
{
reader = new BufferedReader(new InputStreamReader(url.openStream()));
String line;
while ((line = reader.readLine()) != null)
{
parseLine(line, layer);
}
generateBoreholes(data, layer);
layer.loadComplete();
}
catch (IOException e)
{
e.printStackTrace();
return false;
}
finally
{
if (reader != null)
{
try
{
reader.close();
}
catch (IOException e)
{
}
}
}
return true;
}
protected void parseLine(String line, BoreholeLayer layer)
{
Matcher matcher = headerPattern.matcher(line);
if (matcher.matches())
{
currentData = new BoreholeData();
data.add(currentData);
return;
}
//from now on, must be in a well
if (currentData == null)
{
return;
}
matcher = endPattern.matcher(line);
if (matcher.matches())
{
currentData = null;
return;
}
matcher = namePattern.matcher(line);
if (matcher.matches())
{
currentData.name = matcher.group(1);
return;
}
matcher = wrefPattern.matcher(line);
if (matcher.matches())
{
double x = Double.valueOf(matcher.group(1));
double y = Double.valueOf(matcher.group(2));
double z = Double.valueOf(matcher.group(3));
currentData.wref = new Vec4(x, y, z);
return;
}
matcher = vrtxPattern.matcher(line);
if (matcher.matches())
{
double x = Double.valueOf(matcher.group(1));
double y = Double.valueOf(matcher.group(2));
double z = Double.valueOf(matcher.group(3));
Vec4 vrtx = new Vec4(x, y, z);
BoreholeDataPathItem lastItem =
!currentData.path.isEmpty() ? currentData.path.get(currentData.path.size() - 1) : null;
double lastDepth = lastItem != null ? lastItem.depth : 0;
Vec4 lastVrtx = lastItem != null ? lastItem.vertex : currentData.wref;
double depth = lastDepth + lastVrtx.distanceTo3(vrtx);
currentData.path.add(new BoreholeDataPathItem(depth, vrtx));
return;
}
matcher = stationPattern.matcher(line);
if (matcher.matches())
{
double depth = Double.valueOf(matcher.group(1));
Angle pitch = Angle.fromDegrees(Double.valueOf(matcher.group(2)));
Angle heading = Angle.fromDegrees(Double.valueOf(matcher.group(3)));
BoreholeDataPathItem lastItem =
!currentData.path.isEmpty() ? currentData.path.get(currentData.path.size() - 1) : null;
double lastDepth = lastItem != null ? lastItem.depth : 0;
Vec4 lastVrtx = lastItem != null ? lastItem.vertex : currentData.wref;
Angle currentPitch =
currentData.lastStationPitch != null ? Angle.mix(0.5, currentData.lastStationPitch, pitch) : pitch;
Angle currentHeading =
currentData.lastStationHeading != null ? Angle.mix(0.5, currentData.lastStationHeading, heading)
: heading;
Vec4 vrtx = new Vec4(0, 0, lastDepth - depth);
Quaternion q = Quaternion.fromRotationXYZ(currentPitch, Angle.ZERO, currentHeading.multiply(-1));
vrtx = vrtx.transformBy3(q);
vrtx = lastVrtx.add3(vrtx);
currentData.path.add(new BoreholeDataPathItem(depth, vrtx));
currentData.lastStationPitch = pitch;
currentData.lastStationHeading = heading;
}
matcher = pathPattern.matcher(line);
if (matcher.matches())
{
double depth = Double.valueOf(matcher.group(1));
double z = Double.valueOf(matcher.group(2));
double x = Double.valueOf(matcher.group(3));
double y = Double.valueOf(matcher.group(4));
Vec4 path = new Vec4(x + currentData.wref.x, y + currentData.wref.y, z);
currentData.path.add(new BoreholeDataPathItem(depth, path));
return;
}
matcher = tvssPathPattern.matcher(line);
if (matcher.matches())
{
double depth = Double.valueOf(matcher.group(1));
double z = Double.valueOf(matcher.group(2));
double x = Double.valueOf(matcher.group(3));
double y = Double.valueOf(matcher.group(4));
Vec4 path = new Vec4(x + currentData.wref.x, y + currentData.wref.y, z);
currentData.path.add(new BoreholeDataPathItem(depth, path));
return;
}
matcher = tvdPathPattern.matcher(line);
if (matcher.matches())
{
double depth = Double.valueOf(matcher.group(1));
double z = Double.valueOf(matcher.group(2));
double x = Double.valueOf(matcher.group(3));
double y = Double.valueOf(matcher.group(4));
Vec4 path = new Vec4(x + currentData.wref.x, y + currentData.wref.y, z - currentData.wref.z);
currentData.path.add(new BoreholeDataPathItem(depth, path));
return;
}
matcher = zonePattern.matcher(line);
if (matcher.matches())
{
String name = matcher.group(1);
double depth1 = Double.valueOf(matcher.group(2));
double depth2 = Double.valueOf(matcher.group(3));
currentData.zones.add(new BoreholeDataZone(name, depth1, depth2));
return;
}
matcher = mrkrPattern.matcher(line);
if (matcher.matches())
{
String name = matcher.group(1);
double depth = Double.valueOf(matcher.group(3));
currentData.markers.add(new BoreholeDataMarker(name, depth));
return;
}
matcher = normPattern.matcher(line);
if (matcher.matches())
{
if (currentData.markers.isEmpty())
{
return;
}
BoreholeDataMarker marker = currentData.markers.get(currentData.markers.size() - 1);
double x = Double.valueOf(matcher.group(1));
double y = Double.valueOf(matcher.group(2));
double z = Double.valueOf(matcher.group(3));
Vec4 norm = new Vec4(x, y, z).normalize3();
marker.azimuth = Angle.fromRadians(Math.atan2(norm.x, norm.y)); //reversed x,y
marker.dip = Angle.fromRadians(Math.atan2(Math.sqrt(norm.x * norm.x + norm.y * norm.y), norm.z));
return;
}
matcher = dipPattern.matcher(line);
if (matcher.matches())
{
if (currentData.markers.isEmpty())
{
return;
}
BoreholeDataMarker marker = currentData.markers.get(currentData.markers.size() - 1);
//in gradians, convert to degrees
marker.azimuth = Angle.fromDegrees(Double.valueOf(matcher.group(1)) * 90d / 100d);
marker.dip = Angle.fromDegrees(Double.valueOf(matcher.group(2)) * 90d / 100d);
return;
}
matcher = dipDegPattern.matcher(line);
if (matcher.matches())
{
if (currentData.markers.isEmpty())
{
return;
}
BoreholeDataMarker marker = currentData.markers.get(currentData.markers.size() - 1);
marker.azimuth = Angle.fromDegrees(Double.valueOf(matcher.group(1)));
marker.dip = Angle.fromDegrees(Double.valueOf(matcher.group(2)));
return;
}
}
protected void generateBoreholes(List<BoreholeData> dataList, BoreholeLayer layer)
{
CoordinateTransformation transformation = layer.getCoordinateTransformation();
ColorMap colorMap = layer.getColorMap();
//use same color for same property names (zone/marker) in each borehole
Map<String, Color> colors = new HashMap<String, Color>();
for (BoreholeData data : dataList)
{
for (BoreholeDataZone zone : data.zones)
{
colors.put(zone.name, null);
}
for (BoreholeDataMarker m : data.markers)
{
colors.put(m.name, null);
}
}
int i = 0;
int colorCount = Math.max(1, colors.size() - (colorMap != null ? 1 : 0));
for (Entry<String, Color> entry : colors.entrySet())
{
float hue = i++ / (float) colorCount;
entry.setValue(colorMap != null ? colorMap.calculateColor(hue) : generateColor(hue, entry.getKey()));
}
for (BoreholeData data : dataList)
{
Position position = vertexToPosition(data.wref, transformation);
bounds = Bounds.union(bounds, position);
MarkerAttributes markerAttributes = new BasicMarkerAttributes();
markerAttributes.setShapeType(BasicMarkerShape.CONE);
markerAttributes.setMarkerPixels(4);
BoreholeImpl borehole = new BoreholeImpl(position, markerAttributes);
borehole.setTooltipText(data.name);
layer.addBorehole(borehole);
for (BoreholeDataPathItem item : data.path)
{
Position pathPosition = vertexToPosition(item.vertex, transformation);
borehole.addPath(item.depth, pathPosition);
}
for (BoreholeDataZone zone : data.zones)
{
BoreholeSampleImpl sample = new BoreholeSampleImpl(borehole);
sample.setText(zone.name);
sample.setColor(colors.get(zone.name));
sample.setDepthFrom(zone.depth1);
sample.setDepthTo(zone.depth2);
borehole.addSample(sample);
}
for (BoreholeDataMarker m : data.markers)
{
Position markerPosition = borehole.getPath().getPosition(m.depth);
BoreholeMarkerImpl marker = new BoreholeMarkerImpl(borehole, markerPosition);
marker.setDepth(m.depth);
marker.setTooltipText(m.name);
marker.setColor(colors.get(m.name));
marker.setAzimuth(m.azimuth);
marker.setDip(m.dip);
borehole.addMarker(marker);
}
}
}
protected Position vertexToPosition(Vec4 vertex, CoordinateTransformation transformation)
{
if (transformation != null)
{
double[] transformed = new double[3];
transformation.TransformPoint(transformed, vertex.x, vertex.y, vertex.z);
return Position.fromDegrees(transformed[1], transformed[0], transformed[2]);
}
return Position.fromDegrees(vertex.y, vertex.x, vertex.z);
}
protected Color generateColor(float hue, String s)
{
int hash = s.hashCode();
Random random = new Random(hash);
int fourteenbit = 0x4000;
int number = random.nextInt(fourteenbit);
int sat = 127 + (number & 0x7f); //7 bits
int val = 127 + ((number >> 7) & 0x7f); //7 bits
return new Color(Color.HSBtoRGB(hue, sat / 255f, val / 255f));
}
private static class BoreholeData
{
public Vec4 wref;
public String name;
public final List<BoreholeDataPathItem> path = new ArrayList<BoreholeDataPathItem>();
public final List<BoreholeDataMarker> markers = new ArrayList<BoreholeDataMarker>();
public final List<BoreholeDataZone> zones = new ArrayList<BoreholeDataZone>();
public Angle lastStationPitch;
public Angle lastStationHeading;
}
private static class BoreholeDataPathItem
{
public final double depth;
public final Vec4 vertex;
public BoreholeDataPathItem(double depth, Vec4 vertex)
{
this.depth = depth;
this.vertex = vertex;
}
}
private static class BoreholeDataMarker
{
public final String name;
public final double depth;
public Angle azimuth;
public Angle dip;
public BoreholeDataMarker(String name, double depth)
{
this.name = name;
this.depth = depth;
}
}
private static class BoreholeDataZone
{
public final String name;
public final double depth1;
public final double depth2;
public BoreholeDataZone(String name, double depth1, double depth2)
{
this.name = name;
this.depth1 = depth1;
this.depth2 = depth2;
}
}
}