Skip to content

Instantly share code, notes, and snippets.

@alwarren
Created October 19, 2020 18:04
Show Gist options
  • Save alwarren/5faf55dd3456ab631e3bc95a7cf720dd to your computer and use it in GitHub Desktop.
Save alwarren/5faf55dd3456ab631e3bc95a7cf720dd to your computer and use it in GitHub Desktop.
Parsing Metar data in CSV format from NOAA's Text Data Server
object CsvMapper {
fun decode(rawText: String): Metar {
val values = rawText.split(",")
return buildMetar(values, skyConditions(values), qualityControlFlags(values))
}
private fun buildMetar(values: List<String>, skyCondition: List<SkyCondition>, qualityControlFlags: QualityControlFlags): Metar {
// 0:raw_text 1:station_id 2:observation_time 3:latitude 4:longitude 5:temp_c 6:dewpoint_c 7:wind_dir_degrees
// 8:wind_speed_kt 9:wind_gust_kt 10:visibility_statute_mi 11:altim_in_hg 12:sea_level_pressure_mb
// 13:quality_control_flags
// 14:wx_string
// 15:sky_condition
// 16:flight_category 17:three_hr_pressure_tendency_mb 18:maxT_c 19:minT_c 20:maxT24hr_c 21:minT24hr_c
// 22:precip_in 23:pcp3hr_in 24:pcp6hr_in 25:pcp24hr_in 26:snow_in 27:vert_vis_ft 28:metar_type 29:elevation_m
val metar = Metar(
values[0], values[1], values[2], values[3].toFloatOrNull(), values[4].toFloatOrNull(), values[5].toFloatOrNull(),
values[6].toFloatOrNull(), values[7].toIntOrNull(), values[8].toIntOrNull(), values[9].toIntOrNull(),
values[10].toFloatOrNull(), values[11].toFloatOrNull(), values[12].toFloatOrNull(),
qualityControlFlags, values[14],
skyCondition, values[16], values[17].toFloatOrNull(), values[18].toFloatOrNull(), values[19].toFloatOrNull(),
values[20].toFloatOrNull(), values[21].toFloatOrNull(), values[22].toFloatOrNull(), values[23].toFloatOrNull(),
values[24].toFloatOrNull(), values[25].toFloatOrNull(), values[26].toFloatOrNull(), values[27].toFloatOrNull(),
values[28], values[29].toFloatOrNull()
)
return metar
}
private fun skyConditions(values: List<String>): List<SkyCondition> {
val skies = mutableListOf<SkyCondition>()
val skyConditionIndices = (22..29).toList()
for(index in 0 until skyConditionIndices.size-1 step 2) {
val condition = values[skyConditionIndices[index]]
val altitude = values[skyConditionIndices[index + 1]]
if (condition.isNotEmpty())
skies.add(
SkyCondition(
condition,
if(altitude.isNotEmpty())
Integer.parseInt(altitude) else null
)
)
}
return skies
}
private fun qualityControlFlags(values: List<String>): QualityControlFlags {
// 13..20
return QualityControlFlags(
values[13], values[14], values[15], values[16], values[17], values[18],
values[19], values[20])
}
}
class CsvResponse(responseText: String) {
val errors: String
val warnings: String
val timeTaken: String
val dataSource: String
val numResults: String
val data: List<Metar>
private val lines = responseText.trim().split("\n")
init {
validateResponseText()
errors = lines[0]
warnings = lines[1]
timeTaken = lines[2]
dataSource = lines[3]
numResults = lines[4]
data = getMetars()
}
private fun getMetars(): List<Metar> {
val indices = 6 until lines.size
val airports = mutableListOf<Metar>()
for (i in indices)
airports.add(CsvMapper.decode(lines[i]))
return airports
}
private fun validateResponseText() {
when {
lines.isEmpty() -> throw FileParseException("Empty server response")
lines.size < 5 -> throw FileParseException("Invalid server response")
lines.size == 6 -> throw FileParseException("Missing server data")
}
}
}
class FileParseException(message:String): Exception(message)
package com.ntxdroid.metar.domain.entity
data class Metar(
val rawText: String?,
val stationId: String?,
val observationTime: String?,
val latitude: Float?, // float
val longitude: Float?, // float
val tempC: Float?, // float
val dewpointC: Float?, // float
val windDirDegrees: Int?, // int
val windSpeedKt: Int?, // int
val windGustKt: Int?, // int
val visibilityStatuteMi: Float?, // float
val altimInHg: Float?, // float
val seaLevelPressureMb: Float?, // float
val qualityControlFlags: QualityControlFlags?,
val wxString: String?,
val skyCondition: List<SkyCondition>?,
val flightCategory: String?,
val threeHrPressureTendencyMb: Float?, // float
val maxTC: Float?, // float
val minTC: Float?, // float
val maxT24hrC: Float?, // float
val minT24hrC: Float?, // float
val precipIn: Float?, // float
val pcp3hrIn: Float?, // float
val pcp6hrIn: Float?, // float
val pcp24hrIn: Float?, // float
val snowIn: Float?, // float
val vertVisFt: Float?, // float
val metarType: String?,
val elevationM: Float? // float
)
data class QualityControlFlags (
val autoStation: String?,
val auto: String?,
val presentWeatherSensorOff: String?,
val noSignal: String?,
val maintenanceIndicatorOn: String?,
val lightningSensorOff: String?,
val freezingRainSensorOff: String?,
val corrected: String?
)
data class SkyCondition (
val skyCover: String,
val cloudBaseFtAgl: Int? = null
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment